UnitStatusFire is a UnitStatus-derived component that applies and manages a burning status on a Unit. It tracks base and stacked fire damage over time, applies periodic damage to the afflicted unit (player or enemy), handles stacking/expiration of temporary burn damage entries, and can spread fire to colliding units.
using Sandbox;
using Sandbox.UI;
using System;
using System.Numerics;
public struct StackBurnData
{
public float damage;
public float time;
public float lifetime;
public StackBurnData( float _damage, float _time, float _lifetime )
{
damage = _damage;
time = _time;
lifetime = _lifetime;
}
}
public class UnitStatusFire : UnitStatus
{
private TimeSince _timeSinceDamage;
private const float DAMAGE_INTERVAL = 0.95f;
public float SpreadChance { get; set; }
public bool CanStack { get; set; }
public Enemy EnemySource { get; set; }
public EnemyType EnemyType { get; set; }
public Player PlayerSource { get; set; }
public float Damage { get; private set; }
public float BaseDamage { get; private set; }
private TimeSince _damageOtherTime;
public bool StackDmgMode { get; set; }
private Queue<StackBurnData> _stackTimes = new();
public UnitStatusFire()
{
}
public override void Init( Unit unit )
{
base.Init( unit );
Unit.SetStatusBurning( true );
_timeSinceDamage = 0f;
}
public void SetStartingDamage( float damage )
{
BaseDamage = damage;
Damage = damage;
}
public override void Update( float dt )
{
base.Update( dt );
if ( !Unit.IsValid() || Manager.Instance.IsGameOver )
return;
//Gizmo.Draw.Color = Color.Red;
//Gizmo.Draw.Text( $"{Damage}\n{BaseDamage}\n{_stackTimes.Count()}", new global::Transform( Unit.WorldPosition ) );
if ( _stackTimes.Count > 0 )
{
var data = _stackTimes.First();
//DebugOverlay.Text($"{Damage}\n{string.Format("{0:0.0}", Time.Now - data.time)} / {string.Format("{0:0.0}", data.lifetime)}", Unit.Position, 0f, float.MaxValue);
if ( Time.Now > data.time + data.lifetime )
{
_stackTimes.Dequeue();
RefreshStackDmg();
ElapsedTime = 0f;
}
}
else
{
if ( ElapsedTime > Lifetime )
Unit.RemoveUnitStatus( this );
//DebugOverlay.Text($"{Damage}\n{string.Format("{0:0.0}", ElapsedTime)} / {string.Format("{0:0.0}", Lifetime)}", Unit.Position, 0f, float.MaxValue);
}
if ( _timeSinceDamage > DAMAGE_INTERVAL )
{
if( IsOnEnemy )
{
if ( Enemy.IsValid() )
Enemy.DamageRpc( Damage, PlayerSource, DamageType.Fire, Enemy.WorldPosition.WithZ( 30f ), Vector2.Zero, isCrit: false, shouldFlinch: false );
}
else
{
if( Player.IsValid() )
{
var isSelfInflicted = PlayerSource.IsValid() && PlayerSource == Player;
var damageFlags = PlayerDamageFlags.None;
if ( isSelfInflicted )
damageFlags |= PlayerDamageFlags.SelfInflicted;
Player.Damage( Damage, damageType: DamageType.Fire, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 1f, EnemySource, EnemyType, damageFlags: damageFlags );
}
}
_timeSinceDamage = 0f;
}
}
public void AddDamageStack( float damage, float lifetime )
{
_stackTimes.Enqueue( new StackBurnData( damage, Time.Now, lifetime ) );
RefreshStackDmg();
}
void RefreshStackDmg()
{
Damage = BaseDamage;
foreach ( var data in _stackTimes )
Damage += data.damage;
//Damage = PlayerSource.Stats[PlayerStat.FireDamage];
//foreach ( var data in _stackTimes )
// Damage += data.damage;
//Damage *= PlayerSource.GetDamageMultiplier();
// todo: change particle appearance as stacks increase
}
public override void OnRemove( bool playEffects = true )
{
Unit.SetStatusBurning( false );
}
public override void Refresh()
{
base.Refresh();
ElapsedTime = 0f;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( Manager.Instance.IsGameOver )
return;
if ( _damageOtherTime > DAMAGE_INTERVAL )
{
if ( other is Unit unit && !unit.IsDying && !unit.IsBurning && !unit.IsInTheAir )
{
Vector2 dir = (unit.Position2D - Unit.Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
? (unit.Position2D - Unit.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: 0f, force: 0f, ragdollForce: 0.1f, EnemySource, EnemyType, damageFlags: damageFlags );
}
if ( Game.Random.Float( 0f, 1f ) < SpreadChance )
unit.Ignite( PlayerSource, EnemySource, EnemyType, Damage, Lifetime, SpreadChance, CanStack );
_damageOtherTime = 0f;
}
}
}
}