things/enemies/Crate.cs
using Sandbox;
using SpriteTools;

public class Crate : Enemy
{
	public override bool CanBleed => false;

	public bool IsMysteryBox { get; set; }

	[Property] public Sprite MysteryBoxSprite { get; set; }

	protected override void OnAwake()
	{
		//OffsetY = -0.4f;
		ShadowScale = 1.3f;
		ShadowFullOpacity = 0.8f;
		ShadowOpacity = 0f;

		Scale = 0.95f;

		base.OnAwake();

		//AnimSpeed = 2f;
		//BasePivotY = 0.05f;

		//Sprite.Texture = Texture.Load("textures/sprites/crate.vtex");

		//ScaleFactor = 0.95f;
		//Sprite.Size = new Vector2( 1f, 1f ) * ScaleFactor;

		PushStrength = 8f;
		Deceleration = 15f;

		Radius = 0.25f;
		Health = 45f;

		if ( Manager.Instance.Difficulty < 0 )
			Health = 35f;

		MaxHealth = Health;

		CanTurn = false;
		CanAttack = false;
		FlipX = false;

		Sprite.PlayAnimation( AnimSpawnPath );

		AnimIdlePath = "idle";

		if ( IsProxy )
			return;

		CollideWith.Add( typeof( Enemy ) );
		CollideWith.Add( typeof( Player ) );
	}

	public void BecomeMysteryBox()
	{
		Sprite.Sprite = MysteryBoxSprite;
		IsMysteryBox = true;

		TintFullHp = new Color( 1f, 0.8f, 1f );
		TintZeroHp = new Color( 0.5f, 0.2f, 0.5f );
		TintDeath = new Color( 0.6f, 0.3f, 0.6f );
		Sprite.Tint = TintFullHp;

		FlipX = false;
	}

	protected override void UpdatePosition( float dt )
	{
		base.UpdatePosition( dt );

		WorldPosition += (Vector3)Velocity * dt;
	}

	public override void Colliding( Thing other, float percent, float dt )
	{
		base.Colliding( other, percent, dt );

		if ( other is Enemy enemy && !enemy.IsDying )
		{
			var spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );
			Velocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;
		}
		else if ( other is Player player )
		{
			if ( !player.IsDead )
			{
				Velocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;
			}
		}
	}

	public override void DropLoot( Player player )
	{
		float RAND_POS = 0.2f;

		int num_coins = Game.Random.Int( 2, (IsMysteryBox ? 7 : 3) );
		for ( int i = 0; i < num_coins; i++ )
		{
			var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
			var vel = (pos - Position2D) * Game.Random.Float( 2f, 7f );
			Manager.Instance.SpawnCoin( pos, vel, value: Game.Random.Int( CoinValueMin, CoinValueMax ), force: true );
		}

		var health_pack_chance = player != null ? Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.2f, 0.75f ) : 0.1f;
		if ( IsMysteryBox )
		{
			health_pack_chance = 1f;
		}
		else
		{
			if ( Manager.Instance.Difficulty == -1 )
				health_pack_chance *= 1.3f;
			else if ( Manager.Instance.Difficulty >= 3 )
				health_pack_chance *= 0.7f;
		}

		int numHealthPacks = IsMysteryBox ? Game.Random.Int( 1, 2 ) : 1;
		for( int i = 0; i < numHealthPacks; i++ )
		{
			if ( Game.Random.Float( 0f, 1f ) < health_pack_chance )
			{
				var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
				var vel = (pos - Position2D) * Game.Random.Float( 2f, 6f );
				Manager.Instance.SpawnHealthPack( pos, vel );
			}
		}

		if ( Manager.Instance.TimeSinceMagnet > 50f || IsMysteryBox )
		{
			var magnet_chance = 0.09f * Utils.Map( Manager.Instance.TimeSinceMagnet, 50f, Utils.Map(Manager.Instance.Difficulty, 0, 3, 480f, 750f), 1f, 5.5f, EasingType.Linear );
			if ( IsMysteryBox )
			{
				magnet_chance = 1f;
			}
			else
			{
				if ( Manager.Instance.Difficulty == -1 )
					magnet_chance *= 1.3f;
				else if ( Manager.Instance.Difficulty > 0 )
					magnet_chance *= 0.85f;
				else if ( Manager.Instance.Difficulty > 0 )
					magnet_chance *= 0.5f;
			}

			if ( Game.Random.Float( 0f, 1f ) < magnet_chance )
			{
				var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
				var vel = (pos - Position2D) * Game.Random.Float( 2f, 6f );
				Manager.Instance.SpawnMagnet( pos, vel );
			}
		}

		//var revive_chance = Manager.Instance.GetPlayers( alive: false ).Count() * 0.4f;
		//if ( Game.Random.Float( 0f, 1f ) < revive_chance )
		//{
		//	var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
		//	Manager.Instance.SpawnReviveSoul( pos, vel: (pos - Position2D) * Game.Random.Float( 2f, 6f ) );
		//}

		int numGrenadeChances = IsMysteryBox ? Game.Random.Int( 1, 6 ) : Game.Random.Int(1, (Manager.Instance.Difficulty >= 3 ? 5 : 2));
		for( int i = 0; i < numGrenadeChances; i++ )
		{
			var grenade_chance = IsMysteryBox ? 1f : 0.125f;
			if ( player != null && Game.Random.Float( 0f, 1f ) < grenade_chance )
			{
				var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
				var vel = (pos - Position2D) * Game.Random.Float( 2f, 6f );
				player.SpawnGrenade( pos, vel );
			}
		}

		if( Manager.Instance.Difficulty >= 3 )
		{
			float reroll_chance = IsMysteryBox ? 0.75f : 0.22f;
			int num_chances = IsMysteryBox ? 6 : 2;
			for( int i = 0; i < num_chances; i++ )
			{
				if(Game.Random.Float(0f, 1f) < reroll_chance)
				{
					var pos = Position2D + new Vector2( Game.Random.Float( -RAND_POS, RAND_POS ), Game.Random.Float( -RAND_POS, RAND_POS ) );
					var vel = (pos - Position2D) * Game.Random.Float( 2f, 6f );
					Manager.Instance.SpawnRerollPickup( pos, vel );
				}
			}
		}

		if(IsMysteryBox)
			SpawnMysteryEnemy();
	}

	void SpawnMysteryEnemy()
	{
		List<(TypeDescription Type, float Weight)> enemyTypes = new List<(TypeDescription, float)>
		{
			(TypeLibrary.GetType( typeof( Exploder ) ), 10f),
			(TypeLibrary.GetType( typeof( ExploderElite ) ), 2f),
			(TypeLibrary.GetType( typeof( Spitter ) ), 8f),
			(TypeLibrary.GetType( typeof( SpitterElite ) ), 2f),
			(TypeLibrary.GetType( typeof( Charger ) ), 6f),
			(TypeLibrary.GetType( typeof( ChargerElite ) ), 1f),
			(TypeLibrary.GetType( typeof( Spiker ) ), 6f),
			(TypeLibrary.GetType( typeof( SpikerElite ) ), 0.5f),
			(TypeLibrary.GetType( typeof( Zombie ) ), 0.1f),
			(TypeLibrary.GetType( typeof( ZombieElite ) ), 2f),
			(TypeLibrary.GetType( typeof( Runner ) ), 4f),
			(TypeLibrary.GetType( typeof( RunnerElite ) ), 0.5f)
		};

		if(Manager.Instance.Difficulty >= 2)
		{
			enemyTypes.Add( (TypeLibrary.GetType( typeof( ExploderSpecial ) ), 1f) );
			enemyTypes.Add( (TypeLibrary.GetType( typeof( SpitterSpecial ) ), 1f) );
			enemyTypes.Add( (TypeLibrary.GetType( typeof( SpitterEliteSpecial ) ), 1f) );
			enemyTypes.Add( (TypeLibrary.GetType( typeof( ChargerSpecial ) ), 1f) );
			enemyTypes.Add( (TypeLibrary.GetType( typeof( SpikerSpecial ) ), 1f) );
			enemyTypes.Add( (TypeLibrary.GetType( typeof( RunnerEliteSpecial ) ), 1f) );
		}

		TypeDescription chosenType = null;

		float totalWeight = enemyTypes.Sum( x => x.Weight );
		var rand = Game.Random.Float( 0f, totalWeight );

		for ( int i = enemyTypes.Count - 1; i >= 0; i-- )
		{
			var (type, weight) = enemyTypes[i];
			rand -= weight;

			if ( rand < 0f )
			{
				chosenType = type;
				break;
			}
		}

		var enemy = Manager.Instance.SpawnEnemy( chosenType, Position2D, forceSpawn: true );

		float charmChance = 0.25f;

		if ( Manager.Instance.Difficulty == -1 )
			charmChance *= 2.5f;

		if ( Game.Random.Float( 0f, 1f ) < charmChance )
		{
			enemy.Charm();
			enemy.Flash( 0f );
		}
	}
}