Transposer/Entities/BloodParticle.cs
namespace Sandbox.Transposer;

/// <summary>
/// Physics-based single red pixel that falls with gravity, bounces off X bounds,
/// and converts into a <see cref="Trail"/> when it lands.
/// Spawned constantly by the player as blood drips.
/// </summary>
public class BloodParticle : Entity
{
	private Vector2 _velocity;
	// Tracks net downward displacement remaining before landing. Starts positive
	// (pixels to fall) and is decremented by downward movement each frame.
	// Upward movement (for spurts) temporarily increases it, so the particle
	// lands that many pixels below its spawn point regardless of arc height.
	private float _distanceToFall;
	private float _gravity;
	private float _opacity;
	private bool _playNoise;

	public BloodParticle( int x, int y, Vector2 velocity, float distanceToFall, float gravity,
		TransposerScene scene, bool playNoise = false )
	{
		PixelPosition = new PixelPoint( x, y );
		_scene = scene;
		_type = "bloodparticle";
		_layer = Globals.DEPTH_BLOOD_PARTICLE;
		_velocity = velocity;
		_distanceToFall = distanceToFall;
		_gravity = gravity;
		_opacity = Game.Random.Float( 0.65f, 0.9f );
		_playNoise = playNoise;
	}

	public override void UpdateEntity( float deltaTime )
	{
		base.UpdateEntity( deltaTime );

		float yMove = _velocity.y * deltaTime;
		float newY = ExactY + yMove;

		if ( IsInBounds( PixelY, (int)MathF.Round( newY ), 3 ) )
		{
			ExactY = newY;
		}
		else
		{
			Land();
			return;
		}

		float xMove = _velocity.x * deltaTime;
		float newX = ExactX + xMove;

		if ( IsInBounds( (int)MathF.Round( newX ), PixelY, 3 ) )
		{
			ExactX = newX;
		}
		else
		{
			if ( _velocity.x > 0 ) PixelX--;
			_velocity = new Vector2( 0, _velocity.y );
		}

		_distanceToFall += yMove;
		if ( _distanceToFall <= 0f )
		{
			if ( _playNoise )
				AudioManager.PlaySfx( Sfx.BloodSplat );
			Land();
			return;
		}

		// Light air resistance — only applied while moving in the "positive" direction
		// (upward for Y, rightward for X). Downward/leftward motion is not damped.
		if ( _velocity.y > 0f )
			_velocity = new Vector2( _velocity.x, _velocity.y * 0.99f );
		if ( _velocity.x > 0f )
			_velocity = new Vector2( _velocity.x * 0.96f, _velocity.y );

		_velocity = new Vector2( _velocity.x, _velocity.y + _gravity );
	}

	public override void Draw()
	{
		Color32 color = new( 255, 0, 0, (byte)(_opacity * 255f) );
		Screen.AddPixel( PixelX, PixelY, color );
	}

	private void Land()
	{
		_scene.AddEntity( new Trail( PixelX, PixelY, _opacity, _scene ) );
		_scene.RemoveEntity( this );
		((GameScene)_scene).RemoveBloodParticle( this );
	}
}