things/EnemySpike.cs

A game world entity representing a stationary spike projectile/trap used by enemies. It spawns with a background sprite, lives for a short lifetime, plays sfx, checks nearby grid squares for collisions, and damages players or enemies it contacts while tracking which things it has already hit. It also supports being "charmed" to flip allegiance and adjust tint and damage.

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

public class EnemySpike : 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 bool IsCharmed { get; private set; }

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

		//OffsetY = -0.2f;

		Radius = 0.275f;

		Scale = 1.2f;
		//Sprite.Size = new Vector2( 1f, 1f ) * Scale;
		//Sprite.FlipHorizontal = Game.Random.Float( 0f, 1f ) < 0.5f;
		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 = 10f;
		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 ) * 1.3f * Globals.SPRITE_SCALE;
		BgSprite.LocalRotation = new Angles( 0f, -90f, 0f );

		if ( IsProxy )
			return;

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

	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;

		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.2f, 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 && !IsCharmed )
		{
			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(1f, 2f));
			}

			_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( 0.75f, 1.5f ), addTempWeight: 0f, isCrit: false, DamageType.Ranged );

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

			Manager.Instance.PlaySfxNearby( "spike.stab", enemy.Position2D, pitch: Game.Random.Float( 1.1f, 1.2f ), 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 = 10f * Manager.Instance.Player.Stats[PlayerStat.CharmedEnemyDmgDealtMultiplier];
	}
}