LavaPuddle.cs
using Sandbox.UI;
using SpriteTools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using static Manager;

public class LavaPuddle : Component
{
	[Property] public SpriteRendererLayer Sprite { get; set; }

	public TimeSince TimeSinceSpawn { get; set; }
	public float Lifetime { get; set; }
	
	public float Radius { get; set; }
	public float FullRadius { get; set; }

	private float _timeOffset;
	private TimeSince _timeSinceDamagePlayer;
	private const float DAMAGE_INTERVAL = 0.25f;

	private float _expandTime;

	public float DamageToPlayer { get; set; }

	public Color ColorA { get; set; }
	public Color ColorB { get; set; }

	public Vector2 Position2D
	{
		get { return (Vector2)WorldPosition; }
		set { WorldPosition = new Vector3( value.x, value.y, WorldPosition.z ); }
	}

	protected override void OnAwake()
	{
		base.OnAwake();

		if ( Game.Random.Float( 0f, 1f ) < 0.5f )
			Sprite.SpriteFlags = SpriteFlags.HorizontalFlip;

		Radius = 0f;
		LocalScale = new Vector3( Radius * 1f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;

		//LocalScale = new Vector3( Game.Random.Float( 0.4f, 0.6f ), Game.Random.Float( 0.15f, 0.25f ), 1f ) * Globals.SPRITE_SCALE;
		//LocalScale = new Vector3( Game.Random.Float( 0.33f, 0.48f ), Game.Random.Float( 0.4f, 0.63f ), 1f ) * 5f * Globals.SPRITE_SCALE;

		_timeOffset = Game.Random.Float( 0f, 99f );

		TimeSinceSpawn = 0f;
		_expandTime = Game.Random.Float( 0.4f, 0.5f );

		DamageToPlayer = 2f + Math.Min(Manager.Instance.Difficulty, 5);
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		float FADE_IN_TIME = 0.3f;
		var color = Color.Lerp( ColorA, ColorB, 0.5f + Utils.FastSin( TimeSinceSpawn * 16f ) * 0.5f );
		var opacity = 0.3f 
			* Utils.Map( TimeSinceSpawn, 0f, FADE_IN_TIME, 0f, 1f, EasingType.SineOut ) 
			* Utils.Map( TimeSinceSpawn, Lifetime - 1f, Lifetime, 1f, 0f, EasingType.SineIn ) 
			* ( 0.85f + Utils.FastSin( _timeOffset + Time.Now * 16f ) * 0.15f );
		Sprite.Tint = color.WithAlpha( opacity );

		//Gizmo.Draw.Color = Color.White.WithAlpha( 0.05f );
		//Gizmo.Draw.LineSphere( WorldPosition, Radius, 20 );

		if (TimeSinceSpawn < _expandTime )
		{
			Radius = Utils.Map( TimeSinceSpawn, 0f, _expandTime, FullRadius * 0.05f, FullRadius, EasingType.QuadOut );
			LocalScale = new Vector3( Radius * 0.98f, Radius * 1.06f, 1f ) * 2f * Globals.SPRITE_SCALE;
		}

		if ( TimeSinceSpawn > Lifetime )
		{
			Manager.Instance.RemoveLavaPuddle( this );
			GameObject.Destroy();
			return;
		}

		if( TimeSinceSpawn > FADE_IN_TIME && TimeSinceSpawn < Lifetime - 0.5f ) 
		{
			var player = Manager.Instance.Player;
			float distSqr = (player.Position2D - Position2D).LengthSquared;

			if( distSqr < MathF.Pow(Radius, 1.9f) && _timeSinceDamagePlayer > DAMAGE_INTERVAL && player.TimeSinceHurtLava > DAMAGE_INTERVAL && !player.IsDead )
			{
				float currDmg = Utils.Map( TimeSinceSpawn, FADE_IN_TIME, FADE_IN_TIME * 2f, 1f, DamageToPlayer );
				float dmg = player.CheckDamageAmount( currDmg, DamageType.LavaPuddle );

				if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
				{
					player.Damage( dmg );

					_timeSinceDamagePlayer = 0f;
					player.TimeSinceHurtLava = 0f;

					Manager.Instance.PlaySfxNearby( "lava_puddle_03", player.Position2D, pitch: Game.Random.Float( 0.9f, 1.1f ), volume: 1f, maxDist: 4f );
				}
			}
		}
	}
}