things/EnemySpikeElite.cs

An enemy projectile/thing called EnemySpikeElite. It spawns with background sprite, moves toward a Target with some steering and lifetime, handles collisions with Player and Enemy applying damage and effects, can be charmed to change tint and damage, and plays SFX.

NetworkingFile Access
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Manager;

public class EnemySpikeElite : Thing
{
	public TimeSince SpawnTime { get; private set; }

	public Enemy Shooter { get; set; }
	public float Damage { get; set; }
	public float Lifetime { get; set; }
	public Sprite2D BgSprite { get; set; }
	//public SpikeBackground Background { get; set; }

	public List<Thing> _hitThings = new List<Thing>();

	private bool _playedSfx;

	public Thing Target { get; set; }
	private float _speedMult;

	public bool IsCharmed { get; private set; }

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

		//OffsetY = -0.24f;

		Radius = 0.43f;

		Scale = 2.1f;
		//Sprite.Size = new Vector2( 1f, 1f ) * Scale;
		Sprite.SpriteFlags = Game.Random.Int( 0, 1 ) == 0 ? SpriteFlags.HorizontalFlip : SpriteFlags.None;

		Sprite.LocalScale = new Vector3( 1f ) * Scale * Globals.SPRITE_SCALE;

		Lifetime = 1.9f;
		Damage = 24f;
		SpawnTime = 0f;

		// spawn background
		var bgObj = Manager.Instance.EnemySpikeBgPrefab.Clone( WorldPosition );
		bgObj.SetParent( GameObject );
		//bgObj.Transform.LocalPosition = new Vector3( 0f, OffsetY, Globals.SHADOW_DEPTH_OFFSET );
		bgObj.LocalPosition = new Vector3( 0f, 0f, Globals.SHADOW_DEPTH_OFFSET );
		bgObj.NetworkMode = NetworkMode.Never;

		BgSprite = bgObj.Components.Get<Sprite2D>();
		BgSprite.SpriteFlags = Game.Random.Int( 0, 1 ) == 0 ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
		BgSprite.LocalScale = new Vector3( 1f ) * 2.2f * Globals.SPRITE_SCALE;
		BgSprite.LocalRotation = new Angles( 0f, -90f, 0f );

		if ( IsProxy )
			return;

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

		_speedMult = Game.Random.Float( 0.3f, 2.4f );
	}

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

		//Gizmo.Draw.Color = SpawnTime < 1.25f || SpawnTime > 1.6f ? Color.White.WithAlpha( 0.05f ) : Color.Red.WithAlpha( 0.1f );
		//Gizmo.Draw.LineSphere( (Vector3)Position2D, Radius );

		float dt = Time.Delta;

		if ( Manager.Instance.Difficulty >= 1 && Target != null )
		{
			var speed = Utils.Map( SpawnTime, 0f, 0.7f, 3f, 0f, EasingType.SineIn ) * _speedMult;

			if(Target.IsValid())
				Velocity += ((Target.Position2D + Target.Velocity * Game.Random.Float( 0f, 2.5f )) - Position2D).Normal * speed * dt;

			Position2D += Velocity * dt;

			Position2D = Manager.Instance.ClampToBounds( Position2D );

			var deceleration = Utils.Map( SpawnTime, 0.3f, 0.8f, 0.75f, 5f, EasingType.SineIn );
			Velocity *= (1f - dt * deceleration);
		}

		WorldPosition = WorldPosition.WithZ( Globals.GetZPos( Position2D.y ) );

		if ( SpawnTime > Lifetime )
		{
			Remove();
			return;
		}

		if ( !Manager.Instance.ShouldUpdateThings )
			return;

		if ( !_playedSfx && SpawnTime > 1.15f )
		{
			Manager.Instance.PlaySfxNearby( "spike.thrust", Position2D, pitch: Game.Random.Float( 1.15f, 1.3f ), volume: 1.25f, maxDist: 6f );
			_playedSfx = true;
		}

		for ( int dx = -1; dx <= 1; dx++ )
		{
			for ( int dy = -1; dy <= 1; dy++ )
			{
				Manager.Instance.HandleThingCollisionForGridSquare( this, new GridSquare( GridPos.x + dx, GridPos.y + dy ), dt );

				if ( IsRemoved )
					return;
			}
		}
	}

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

		if ( SpawnTime < 1.25f || SpawnTime > 1.6f )
			return;

		if ( other is Player player && !player.IsDead )
		{
			if ( _hitThings.Contains( player ) )
				return;

			float dmg = player.CheckDamageAmount( Damage, DamageType.Ranged );

			if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
			{
				Manager.Instance.PlaySfxNearby( "spike.stab", player.Position2D, pitch: Game.Random.Float( 0.85f, 0.9f ), volume: 1.6f, maxDist: 6f );
				player.Damage( dmg );
				player.AddVelocity( (player.Position2D - Position2D).Normal * Game.Random.Float(2f, 4f));
			}

			_hitThings.Add( player );
		}
		else if ( other is Enemy enemy && (IsCharmed != enemy.IsCharmed) )
		{
			if ( _hitThings.Contains( enemy ) )
				return;

			enemy.Damage( Damage, null, addVel: (enemy.Position2D - Position2D).Normal * Game.Random.Float( 1f, 2f ), addTempWeight: 0f, isCrit: false, DamageType.Ranged );

			if ( Shooter.IsValid() )
				enemy.Target = Shooter;

			Manager.Instance.PlaySfxNearby( "spike.stab", enemy.Position2D, pitch: Game.Random.Float( 1.15f, 1.25f ), volume: 1.2f, maxDist: 4.5f );

			_hitThings.Add( enemy );
		}
	}

	public void BecomeCharmed( bool elite = false )
	{
		IsCharmed = true;
		Sprite.Tint = new Color( 1f, 0f, 1f, 0.8f );
		Sprite.FlashTint = new Color( 1f, 0f, 1f, 0.9f );

		BgSprite.Tint = new Color( 1f, 0f, 1f, 0.6f );

		Damage = 30f * Manager.Instance.Player.Stats[PlayerStat.CharmedEnemyDmgDealtMultiplier];
	}
}