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.
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;
}
}