enemyStatus/BurningEnemyStatus.cs

An enemy status component that applies a burning/dot effect to an Enemy. It tracks stacks, damage interval and lifetime, applies periodic fire damage to the host enemy, spreads burn to nearby enemies or players on collision, and manages a burning VFX and sound cues.

File Access
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox;

public class BurningEnemyStatus : EnemyStatus
{
	private TimeSince _sinceDamageTime;
	private const float DAMAGE_INTERVAL = 0.42f;

	public float Lifetime { get; set; }
	public float SpreadChance { get; set; }

	public Player Player { get; set; }

	public float Damage { get; set; }

	private TimeSince _damageOtherTime;

	public static float BURN_REAPPLY_COOLDOWN = 0.5f;

	public int NumStacks { get; set; }

	public BurningEnemyStatus()
	{

	}

	public override void Init( Enemy enemy )
	{
		base.Init( enemy );

		Enemy.CreateBurningVfx();
	}

	public void AddStack( float damage, float lifetime, float spreadChance )
	{
		var maxStacks = (int)Player.Stats[PlayerStat.MaxFireStacks];

		if ( NumStacks >= maxStacks )
		{
			//Refresh();
		}
		else
		{
			Manager.Instance.PlaySfxNearby( "burn", Enemy.Position2D, pitch: Utils.Map(NumStacks, 0, 7, 1.35f, 1.65f), volume: 0.5f, maxDist: 3f );
			NumStacks = Math.Min( NumStacks + 1, maxStacks );

			if ( Enemy.BurningVfx.IsValid() )
				Enemy.BurningVfx.SetStacks( NumStacks );
		}

		Damage = damage;
		Lifetime = lifetime;
		SpreadChance = spreadChance;

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

	public override void Update( float dt )
	{
		if ( Enemy == null || !Enemy.IsValid() )
			return;

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"{NumStacks}\n{ElapsedTime}", new global::Transform( (Vector3)Enemy.Position2D + new Vector3( 0f, -0.5f, 0f ) ) );

		if ( ElapsedTime > Lifetime )
		{
			if(NumStacks > 0)
			{
				NumStacks--;

				if ( Enemy.BurningVfx.IsValid() )
					Enemy.BurningVfx.SetStacks( NumStacks );

				ElapsedTime = 0f;
			}
			else
			{
				Enemy.RemoveEnemyStatus( this );
				return;
			}
		}

		if ( _sinceDamageTime > DAMAGE_INTERVAL )
		{
			Enemy.DamageFire( Damage * (NumStacks + 1), Player );
			_sinceDamageTime = 0f;
		}
	}

	public override void Remove()
	{
		Enemy.RemoveBurningVfx();
	}

	public override void Refresh()
	{
		ElapsedTime = 0f;
	}

	public override void Colliding( Thing other, float percent, float dt )
	{
		bool didDamage = false;

		if ( other is Enemy enemy && !enemy.IsDying && !enemy.HasEnemyStatus( this ) )
		{
			if ( _damageOtherTime > DAMAGE_INTERVAL )
			{
				// should stack damage apply to nearby enemies?
				enemy.DamageFire( Damage, Player );

				if ( Game.Random.Float( 0f, 1f ) < SpreadChance )
				{
					var burning = enemy.GetEnemyStatus<BurningEnemyStatus>();
					if ( burning != null )
					{
						burning.AddStack( Damage, Lifetime, SpreadChance );
					}
					else
					{
						enemy.Burn( Player, Damage, Lifetime, SpreadChance );
						Manager.Instance.PlaySfxNearby( "burn", enemy.Position2D, pitch: Sandbox.Game.Random.Float( 1.15f, 1.35f ), volume: 0.7f, maxDist: 4f );
					}

					enemy.TimeSinceBurn = 0f;
				}

				didDamage = true;
			}
		}
		else if ( other is Player player && !player.IsDead )
		{
			if ( _damageOtherTime > DAMAGE_INTERVAL )
			{
				// should stack damage apply to nearby player?
				player.Damage( Damage );
				Manager.Instance.PlaySfxNearby( "burn", player.Position2D, pitch: Game.Random.Float( 1.15f, 1.35f ), volume: 0.6f, maxDist: 4f );
				didDamage = true;
			}
		}

		if ( didDamage )
			_damageOtherTime = 0f;
	}
}