things/FireGround.cs

A Thing subclass that represents an area-of-effect fire patch. It emits particles, deals periodic fire damage to players and enemies that collide with it, can ignite units to spread, and auto-destroys after its lifetime.

NetworkingFile Access
using System;
using Sandbox;

public class FireGround : Thing
{
	[Property] public ParticleEmitter Emitter { get; set; }

	public float Damage { get; set; }
	[Sync] public float Lifetime { get; set; }

	public Player PlayerSource { get; set; }
	public Enemy EnemySource { get; set; }
	public EnemyType EnemyType { get; set; }

	public float SpreadChance { get; set; }
	public bool CanStack { get; set; }

	private Dictionary<Thing, float> _damageTimes;
	private const float DAMAGE_INTERVAL = 0.5f;

	public bool HurtPlayers { get; set; }
	public bool HurtEnemies { get; set; }

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

		if ( IsProxy )
			return;

		if( HurtPlayers )
			CollideWithTags.Add( "player" );

		if( HurtEnemies )
			CollideWithTags.Add( "enemy" );

		_damageTimes = new();
	}

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

		if ( TimeSinceSpawn > Lifetime - 0.5f )
		{
			Emitter.Rate = 0f;
		}

		if ( IsProxy )
			return;

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"{str}", new global::Transform( WorldPosition ) );

		for(int i = _damageTimes.Count - 1; i >= 0; i-- )
		{
			var pair = _damageTimes.ElementAt(i);
			if ( Time.Now > pair.Value + DAMAGE_INTERVAL )
				_damageTimes.Remove( pair.Key );
		}

		if ( TimeSinceSpawn > Lifetime )
			GameObject.Destroy();
	}

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

		if ( TimeSinceSpawn < 0.35f || TimeSinceSpawn > Lifetime - 0.5f || _damageTimes.ContainsKey(other) || Manager.Instance.IsGameOver )
			return;

		if ( other is Unit unit && !unit.IsDying && !unit.IsBurning && !unit.IsInTheAir )
		{
			if ( other is Enemy e && e.IsSpawning )
				return;

			Vector2 dir = (unit.Position2D - Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
				? (unit.Position2D - Position2D).Normal
				: Utils.GetRandomVector();

			var hitPos = unit.Position2D - dir * unit.Radius;

			if ( unit is Enemy enemy )
			{
				enemy.DamageRpc( Damage, PlayerSource, DamageType.Fire, new Vector3( hitPos.x, hitPos.y, 30f ), Vector2.Zero, isCrit: false, shouldFlinch: false );
			}
			else if ( unit is Player player )
			{
				var isSelfInflicted = PlayerSource.IsValid() && PlayerSource == player;

				var damageFlags = PlayerDamageFlags.None;
				if ( isSelfInflicted )
					damageFlags |= PlayerDamageFlags.SelfInflicted;

				player.DamageRpc( Damage, damageType: DamageType.Fire, hitPos, dir, upwardAmount: Game.Random.Float( 0f, 0.1f ), force: 0f, ragdollForce: 0.1f, EnemySource, enemyType: EnemyType, damageFlags: damageFlags );
			}

			if ( Game.Random.Float( 0f, 1f ) < SpreadChance )
			{
				var igniteLifetime = Lifetime * 0.4f;
				unit.Ignite( PlayerSource, EnemySource, EnemyType, Damage, igniteLifetime, SpreadChance, CanStack );
			}

			_damageTimes.Add( other, Time.Now );
		}
	}

	public void SetScale( float scale )
	{
		var sphereEmitter = GetComponent<ParticleSphereEmitter>();
		sphereEmitter.Radius *= scale;

		var sphereCollider = Collider as SphereCollider;
		sphereCollider.Radius *= scale;

		var particleEffect = GetComponent<ParticleEffect>();
		var range = new ParticleFloat { Type = ParticleFloat.ValueType.Range, ConstantA = 50f * scale, ConstantB = 70f * scale };
		particleEffect.Scale = range;

		Radius = sphereCollider.Radius;
	}
}