PaintSwimming.cs
using Sandbox;
using System;

/// <summary>
/// Paint Swimming (Dive) mechanic — Splatoon-style.
/// When the player stands on/near paint objects (tagged "paint") and holds Shift (Run):
/// - Player model hides
/// - A paint blob visual appears and follows the player
/// - Movement speed increases
/// Releasing Shift or leaving paint area = emerge back to normal.
///
/// Setup in S&Box Inspector:
/// 1. Add this component to the "Player Controller" GameObject
/// 2. Set "Body" = the "Body" child object (with SkinnedModelRenderer)
/// 3. Set "Paint Visual Prefab" = syzygium.prefab (or any paint blob prefab)
/// </summary>
public sealed class PaintSwimming : Component
{
	/// <summary>
	/// The player's 3D body object (child with SkinnedModelRenderer). Will be hidden when diving.
	/// </summary>
	[Property, Category( "References" )] public GameObject Body { get; set; }

	/// <summary>
	/// Prefab to clone as the "paint blob" visual when diving.
	/// Example: syzygium.prefab
	/// </summary>
	[Property, Category( "References" )] public GameObject PaintVisualPrefab { get; set; }

	/// <summary>
	/// Movement speed while swimming in paint.
	/// </summary>
	[Property, Category( "Settings" )] public float SwimSpeed { get; set; } = 450f;

	/// <summary>
	/// Normal walk speed (restored on emerge).
	/// </summary>
	[Property, Category( "Settings" )] public float NormalWalkSpeed { get; set; } = 110f;

	/// <summary>
	/// Normal run speed (restored on emerge).
	/// </summary>
	[Property, Category( "Settings" )] public float NormalRunSpeed { get; set; } = 320f;

	/// <summary>
	/// Radius around the player to search for paint objects.
	/// </summary>
	[Property, Category( "Settings" )] public float DetectRadius { get; set; } = 60f;

	/// <summary>
	/// How fast the swim speed boost decays back to normal after emerging.
	/// Higher values = faster decay. Lower values = smoother, longer slide.
	/// </summary>
	[Property, Category( "Settings" )] public float SpeedDecayRate { get; set; } = 4f;

	[RequireComponent] PlayerController Player { get; set; }

	bool _isDiving;
	public bool IsDiving => _isDiving;
	public TimeSince TimeSinceStoppedDiving { get; private set; } = 10f; // Дефолтне значення, щоб не блокувати стрільбу зі старту

	GameObject _activePaintVisual;

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

		bool onPaint = CheckIfOnPaint();
		bool wantToDive = Input.Down( "Dive" ); // Окрема кнопка, щоб не конфліктувало з присіданням

		if ( onPaint && wantToDive )
		{
			if ( !_isDiving )
				StartDiving();

			UpdateDiveVisual();
		}
		else
		{
			if ( _isDiving )
				StopDiving();
		}

		// Плавно зменшуємо (тушимо) швидкість розгону після виходу з дайву
		if ( !_isDiving )
		{
			if ( Player.WalkSpeed > NormalWalkSpeed )
			{
				Player.WalkSpeed = Player.WalkSpeed + (NormalWalkSpeed - Player.WalkSpeed) * MathF.Min( 1f, Time.Delta * SpeedDecayRate );
				if ( Player.WalkSpeed - NormalWalkSpeed < 0.5f )
					Player.WalkSpeed = NormalWalkSpeed;
			}
			else if ( Player.WalkSpeed < NormalWalkSpeed )
			{
				Player.WalkSpeed = NormalWalkSpeed;
			}

			if ( Player.RunSpeed > NormalRunSpeed )
			{
				Player.RunSpeed = Player.RunSpeed + (NormalRunSpeed - Player.RunSpeed) * MathF.Min( 1f, Time.Delta * SpeedDecayRate );
				if ( Player.RunSpeed - NormalRunSpeed < 0.5f )
					Player.RunSpeed = NormalRunSpeed;
			}
			else if ( Player.RunSpeed < NormalRunSpeed )
			{
				Player.RunSpeed = NormalRunSpeed;
			}
		}
	}

	/// <summary>
	/// Detect paint by finding nearby GameObjects with tag "paint".
	/// Uses a sphere trace that INCLUDES triggers (syzygium has IsTrigger=true).
	/// </summary>
	bool CheckIfOnPaint()
	{
		// Використовуємо фізичне трасування сферою навколо гравця,
		// щоб перевірити перетин з будь-яким колайдером/тригером з тегом "paint" (наприклад, PlaneCollider на нашому префабі слизу).
		var tr = Scene.Trace.Sphere( DetectRadius, WorldPosition, WorldPosition )
			.WithTag( "paint" )
			.HitTriggers()
			.Run();

		if ( tr.Hit && tr.GameObject.IsValid() )
		{
			// Пропускаємо наш власний візуальний ефект дайву
			if ( _activePaintVisual.IsValid() && tr.GameObject == _activePaintVisual ) 
				return false;

			return true;
		}

		return false;
	}

	/// <summary>
	/// Dive into paint — hide body, show paint blob, boost speed.
	/// </summary>
	void StartDiving()
	{
		_isDiving = true;

		// Hide the player's 3D model
		if ( Body.IsValid() )
			Body.Enabled = false;

		// Spawn the paint blob visual from prefab
		if ( PaintVisualPrefab.IsValid() && !_activePaintVisual.IsValid() )
		{
			_activePaintVisual = PaintVisualPrefab.Clone();
			_activePaintVisual.WorldPosition = WorldPosition;
			_activePaintVisual.Enabled = true;

			// Remove "paint" tag from our visual so it doesn't detect itself
			_activePaintVisual.Tags.Remove( "paint" );
			_activePaintVisual.Tags.Add( "dive_visual" );

			// Фарбуємо візуал дайву в колір команди
			var playerTeam = GameObject.Components.Get<PlayerTeam>() ?? GameObject.Components.GetInAncestors<PlayerTeam>() ?? GameObject.Components.GetInDescendants<PlayerTeam>();
			if ( playerTeam.IsValid() )
			{
				var color = playerTeam.TeamColor;
				
				var renderers = _activePaintVisual.Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndDescendants );
				foreach ( var r in renderers ) r.Tint = color;
				
				var props = _activePaintVisual.Components.GetAll<Prop>( FindMode.EverythingInSelfAndDescendants );
				foreach ( var p in props ) p.Tint = color;
				
				var skinnedRenderers = _activePaintVisual.Components.GetAll<SkinnedModelRenderer>( FindMode.EverythingInSelfAndDescendants );
				foreach ( var sr in skinnedRenderers ) sr.Tint = color;
			}
		}

		// Boost speed
		Player.WalkSpeed = SwimSpeed;
		Player.RunSpeed = SwimSpeed;
	}

	/// <summary>
	/// Emerge from paint — show body, destroy paint blob, restore speed.
	/// </summary>
	void StopDiving()
	{
		_isDiving = false;
		TimeSinceStoppedDiving = 0f;

		// Show the player's 3D model
		if ( Body.IsValid() )
			Body.Enabled = true;

		// Remove the paint blob visual
		if ( _activePaintVisual.IsValid() )
		{
			_activePaintVisual.Destroy();
			_activePaintVisual = null;
		}

		// Швидкість більше не скидається миттєво тут!
		// Вона плавно тухне в OnUpdate().
	}

	/// <summary>
	/// Keep the paint blob following the player while diving.
	/// </summary>
	void UpdateDiveVisual()
	{
		if ( !_activePaintVisual.IsValid() ) return;

		_activePaintVisual.WorldPosition = WorldPosition;
		_activePaintVisual.WorldRotation = WorldRotation;
	}

	/// <summary>
	/// Safety: if this component is disabled or destroyed while diving, restore the body and speed.
	/// </summary>
	protected override void OnDisabled()
	{
		if ( _isDiving )
			StopDiving();

		if ( Player.IsValid() )
		{
			Player.WalkSpeed = NormalWalkSpeed;
			Player.RunSpeed = NormalRunSpeed;
		}
	}

	protected override void OnDestroy()
	{
		if ( _isDiving )
			StopDiving();
	}
}