A Thing-derived FireGround area effect that deals periodic fire damage to Units entering it, optionally hurts players or enemies, can ignite units to spread fire, manages particle emitter and lifetime, and scales its collider and particle effect.
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;
}
}