MenuManager.cs
using SpriteTools;

public sealed class MenuManager : Component
{
	public static MenuManager Instance { get; private set; }

	[Property] public GameObject MenuZombiePrefab { get; set; }

	[Property] public SpriteRendererLayer MenuPlayerSprite { get; set; }

	[Property] public Sprite NormalSprite { get; set; }
	[Property] public Sprite EasySprite { get; set; }
	[Property] public Sprite Difficulty1Sprite { get; set; }
	[Property] public Sprite Difficulty2Sprite { get; set; }
	[Property] public Sprite Difficulty3Sprite { get; set; }
	[Property] public Sprite Difficulty4Sprite { get; set; }
	[Property] public Sprite Difficulty5Sprite { get; set; }
	[Property] public Sprite Difficulty6Sprite { get; set; }
	[Property] public Sprite Difficulty7Sprite { get; set; }
	[Property] public Sprite Difficulty8Sprite { get; set; }
	[Property] public Sprite Difficulty9Sprite { get; set; }
	[Property] public Sprite Difficulty10Sprite { get; set; }
	[Property] public Sprite Difficulty11Sprite { get; set; }
	[Property] public Sprite Difficulty12Sprite { get; set; }
	[Property] public Sprite Difficulty13Sprite { get; set; }
	[Property] public Sprite Difficulty14Sprite { get; set; }
	[Property] public Sprite Difficulty15Sprite { get; set; }

	public TimeSince ElapsedTime { get; set; }

	private TimeSince _timeSinceEnemySpawn;
	private float _nextSpawnDelay;

	public const float Y_MIN = -40f;
	public const float Y_MAX = 15f;
	public const float Z_NEAR = -10f;
	public const float Z_FAR = -100f;

	public int HighestDifficultyBeaten { get; set; }

	private TimeSince _timeSinceChangeDifficulty;
	private int _changeDifficultyRand;
	private int _lastDifficulty;

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

		Instance = this;

		ElapsedTime = 0f;

		_timeSinceEnemySpawn = 0f;
		_nextSpawnDelay = 0.01f;

		//Log.Info( $"victory_elapsed_time: {Sandbox.Services.Stats.LocalPlayer.Get( "victory_elapsed_time" ).Min}" );

		//var leaderboard = Sandbox.Services.Leaderboards.GetFromStat( "end_time" );
		//leaderboard.SetAggregationMax();
		//leaderboard.SetSortDescending();
		//leaderboard.Refresh();

		//foreach (var entry in leaderboard.Entries)
		//{
		//	Log.Info( $"{entry.DisplayName}: {entry.Value}" );
		//}

		HighestDifficultyBeaten = GetHighestDifficultyBeaten();

		//HighestDifficultyBeaten = 14;

		//Log.Info( $"HighestDifficultyBeaten: {HighestDifficultyBeaten}" );

		MenuPlayerSprite.PlaybackSpeed = 0.66f;

		var selectedDifficulty = Math.Clamp( (int)Sandbox.Services.Stats.LocalPlayer.Get( "selected_difficulty" ).LastValue, Manager.MinDifficulty, Manager.MaxDifficulty );
		_lastDifficulty = selectedDifficulty;
		SetDifficulty( selectedDifficulty );
	}

	protected override void OnStart()
	{
		for ( int i = 0; i < Game.Random.Int( 3, 15 ); i++ )
			SpawnRandomStartingEnemy();
	}

	protected override void OnUpdate()
	{
		if ( _timeSinceEnemySpawn > _nextSpawnDelay )
		{
			SpawnRandomEnemy();

			_timeSinceEnemySpawn = 0f;
			_nextSpawnDelay = Game.Random.Float( 0.1f, 2f );
		}

		if ( _timeSinceChangeDifficulty < 0.25f )
		{
			if ( _changeDifficultyRand == 0 )
				MenuPlayerSprite.PlayAnimation( "attack_idle" );
			else if ( _changeDifficultyRand == 1 )
				MenuPlayerSprite.PlayAnimation( "hurt_idle" );
			else
				MenuPlayerSprite.PlayAnimation( "hurt_attack_idle" );
		}
		else
		{
			MenuPlayerSprite.PlayAnimation( "idle" );
		}
	}

	void SpawnRandomStartingEnemy()
	{
		bool flipped = Game.Random.Int( 0, 1 ) == 0;
		float y = GetRandomYPos();
		float x = Game.Random.Float( -80f, 80f );
		SpawnEnemy( TypeLibrary.GetType( typeof( MenuZombie ) ), new Vector3( x, y, 0f ), flipped );
	}

	void SpawnRandomEnemy()
	{
		bool flipped = Game.Random.Int( 0, 1 ) == 0;
		float y = GetRandomYPos();
		float x = Game.Random.Float( 100f, 120f ) * Utils.Map( y, Y_MIN, Y_MAX, 1.5f, 1f ) * (flipped ? -1f : 1f);
		SpawnEnemy( TypeLibrary.GetType( typeof( MenuZombie ) ), new Vector3( x, y, 0f ), flipped );

		//var sfx = Sound.Play( "zombie.dirt", new Vector3( x, y, 100f ) );
		//if ( sfx != null )
		//{
		//	sfx.Volume = Utils.Map( y, MenuManager.Y_MIN, MenuManager.Y_MAX, 0.7f, 0.2f ) * Utils.Map( ElapsedTime, 0f, 1.25f, 0f, 1f );
		//	sfx.Pitch = Game.Random.Float( 0.6f, 0.8f );
		//}
	}

	public float GetRandomYPos()
	{
		return Utils.Map( Game.Random.Float( 0f, 1f ), 0f, 1f, Y_MAX, Y_MIN, EasingType.QuartIn );
	}

	void SpawnEnemy( TypeDescription type, Vector3 pos, bool flipped )
	{
		GameObject enemyObj = null;
		MenuEnemy enemy;
		if ( type == TypeLibrary.GetType( typeof( MenuZombie ) ) ) { enemyObj = MenuZombiePrefab.Clone( pos ); }

		enemy = enemyObj.Components.Get<MenuEnemy>();
		enemy.WorldPosition = pos;
		enemyObj.Name = type.ToString();

		//Log.Info( enemy.Sprite );
		enemy.Sprite.SpriteFlags = flipped ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
		enemy.IsFlipped = flipped;
		enemy.SpeedModifier = Game.Random.Float( 4f, 5f ) * Utils.Map( pos.y, Y_MIN, Y_MAX, 1f, 0.1f );
	}

	int GetHighestDifficultyBeaten()
	{
		for ( int difficulty = Manager.MaxDifficulty; difficulty >= 0; difficulty-- )
		{
			//Log.Info( $"victory_elapsed_time: {Sandbox.Services.Stats.LocalPlayer.Get( "victory_elapsed_time" ).Min}" );
			var victoryTime = Sandbox.Services.Stats.LocalPlayer.Get( Manager.GetStatName( difficulty ) ).Min;
			if ( victoryTime > 0f )
			{
				return difficulty;
			}
		}

		return -1;
	}

	public void SetDifficulty( int difficulty )
	{
		if ( difficulty < 0 ) MenuPlayerSprite.Sprite = EasySprite;
		else if ( difficulty == 0 ) MenuPlayerSprite.Sprite = NormalSprite;
		else if ( difficulty == 1 ) MenuPlayerSprite.Sprite = Difficulty1Sprite;
		else if ( difficulty == 2 ) MenuPlayerSprite.Sprite = Difficulty2Sprite;
		else if ( difficulty == 3 ) MenuPlayerSprite.Sprite = Difficulty3Sprite;
		else if ( difficulty == 4 ) MenuPlayerSprite.Sprite = Difficulty4Sprite;
		else if ( difficulty == 5 ) MenuPlayerSprite.Sprite = Difficulty5Sprite;
		else if ( difficulty == 6 ) MenuPlayerSprite.Sprite = Difficulty6Sprite;
		else if ( difficulty == 7 ) MenuPlayerSprite.Sprite = Difficulty7Sprite;
		else if ( difficulty == 8 ) MenuPlayerSprite.Sprite = Difficulty8Sprite;
		else if ( difficulty == 9 ) MenuPlayerSprite.Sprite = Difficulty9Sprite;
		else if ( difficulty == 10 ) MenuPlayerSprite.Sprite = Difficulty10Sprite;
		else if ( difficulty == 11 ) MenuPlayerSprite.Sprite = Difficulty11Sprite;
		else if ( difficulty == 12 ) MenuPlayerSprite.Sprite = Difficulty12Sprite;
		else if ( difficulty == 13 ) MenuPlayerSprite.Sprite = Difficulty13Sprite;
		else if ( difficulty == 14 ) MenuPlayerSprite.Sprite = Difficulty14Sprite;
		else if ( difficulty == 15 ) MenuPlayerSprite.Sprite = Difficulty15Sprite;

		_timeSinceChangeDifficulty = 0f;
		_changeDifficultyRand = Game.Random.Int( 0, 2 );

		if ( _lastDifficulty != difficulty )
		{
			if ( difficulty >= 4 && _lastDifficulty < 4 )
			{
				foreach ( var zombie in Scene.GetAll<MenuZombie>() )
					zombie.Sprite.Sprite = Game.Random.Float( 0f, 1f ) < 0.75f ? zombie.EvolvedSprite : zombie.EvolvedBloodySprite;
			}
			else if ( difficulty < 4 && _lastDifficulty >= 4 )
			{
				foreach ( var zombie in Scene.GetAll<MenuZombie>() )
					zombie.Sprite.Sprite = zombie.NormalSprite;
			}

			_lastDifficulty = difficulty;
		}
	}
}