UnitStatusPoison is a UnitStatus-derived component that applies periodic poison damage to a Unit (player or enemy), tracks accumulated damage, handles interactions with fire, spreading on death, and finishing damage when the status is removed.
using Sandbox;
using System;
using System.Numerics;
public class UnitStatusPoison : UnitStatus
{
public float Damage { get; private set; }
public float FinishDamagePercent { get; private set; }
public float DieSpreadChance { get; private set; }
public float RadiusMultiplier { get; private set; }
public bool IsFlammable { get; private set; }
public float TickTimeModifier { get; private set; }
public int HitsToRemove { get; private set; } = 1;
private int _hitsReceived;
private TimeSince _timeSinceDamage;
private const float DAMAGE_INTERVAL = 2f;
public Enemy EnemySource { get; set; }
public EnemyType EnemyType { get; set; }
public Player PlayerSource { get; set; }
private float _totalDamage;
public UnitStatusPoison()
{
}
public override void Init( Unit unit )
{
base.Init( unit );
Unit.SetStatusPoison( true );
_timeSinceDamage = Game.Random.Float( 0f, 0.25f );
}
public void SetValues( float damage, float finishDmgPercent, float dieSpreadChance, float radiusMultiplier, bool isFlammable, float tickSpeedModifier, int hitsToRemove )
{
if ( damage < Damage )
return;
Damage = damage;
FinishDamagePercent = finishDmgPercent;
DieSpreadChance = dieSpreadChance;
RadiusMultiplier = radiusMultiplier;
IsFlammable = isFlammable;
TickTimeModifier = tickSpeedModifier;
HitsToRemove = hitsToRemove;
}
public override void Update( float dt )
{
base.Update( dt );
if ( !Unit.IsValid() )
return;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{_hitsReceived} / {HitsToRemove}", new global::Transform( Unit.WorldPosition ) );
if ( _timeSinceDamage > DAMAGE_INTERVAL * TickTimeModifier )
{
if ( IsOnEnemy )
{
if ( Enemy.IsValid() )
{
DamageResultFlags damageFlags = DamageResultFlags.None;
if ( PlayerSource.IsValid() )
{
var nearbyIncrease = PlayerSource.GetSyncStat( PlayerStat.PoisonDmgNearbyIncrease );
if( nearbyIncrease > 0f )
{
var radius = PerkPoisonIncreaseNearby.RADIUS * PlayerSource.GetSyncStat( PlayerStat.RadiusMultiplier );
if ( Enemy.WorldPosition.Distance( PlayerSource.WorldPosition ) <= radius )
{
damageFlags |= DamageResultFlags.PoisonIncreaseNearby;
Damage += nearbyIncrease;
}
}
}
Enemy.DamageRpc( Damage, PlayerSource, DamageType.Poison, Enemy.WorldPosition.WithZ( 30f ), Vector2.Zero, isCrit: false, shouldFlinch: false, damageFlags );
_totalDamage += Damage;
}
}
else
{
if ( Player.IsValid() )
{
var isSelfInflicted = PlayerSource.IsValid() && PlayerSource == Player;
var damageFlags = PlayerDamageFlags.None;
if ( isSelfInflicted )
damageFlags |= PlayerDamageFlags.SelfInflicted;
Player.Damage( Damage, damageType: DamageType.Poison, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, 0f, ragdollForce: 0f, EnemySource, enemyType: EnemySource.IsValid() ? EnemySource.EnemyType : EnemyType.None, damageFlags: damageFlags );
_totalDamage += Damage;
}
}
_timeSinceDamage = 0f;
}
}
public override void OnHit( float damage, Player playerSource, Enemy enemySource, DamageType damageType, bool isSelfInflicted )
{
base.OnHit( damage, playerSource, enemySource, damageType, isSelfInflicted );
if ( !(damage > 0f) )
return;
if ( damageType == DamageType.Fire && IsFlammable )
{
if ( playerSource.IsValid() )
Unit.Ignite( playerSource, enemySource: null, enemyType: EnemyType.None, damage, playerSource.GetSyncStat( PlayerStat.FireLifetime ), playerSource.GetSyncStat( PlayerStat.FireSpreadChance ), playerSource.GetSyncStat( PlayerStat.FireDmgStack ) > 0f );
else if ( enemySource.IsValid() )
Unit.Ignite( playerSource: null, enemySource, enemySource.EnemyType, damage, 5f, 0.25f, false );
}
if ( damageType != DamageType.Poison && damageType != DamageType.Self && ElapsedTime > 0.25f )
{
_hitsReceived++;
if ( _hitsReceived < HitsToRemove )
return;
Unit.RemoveUnitStatus( this );
if ( _totalDamage > 0f && FinishDamagePercent > 0f )
{
var dmg = _totalDamage * FinishDamagePercent;
if ( IsOnEnemy )
{
if ( Enemy.IsValid() )
{
Enemy.DamageRpc( dmg, PlayerSource, DamageType.PoisonFinish, Enemy.WorldPosition.WithZ( 30f ), Vector2.Zero, isCrit: false, shouldFlinch: false );
}
}
else
{
if ( Player.IsValid() )
{
var poisonIsSelfDmg = PlayerSource.IsValid() && PlayerSource == Player;
var damageFlags = PlayerDamageFlags.None;
if ( poisonIsSelfDmg )
damageFlags |= PlayerDamageFlags.SelfInflicted;
Player.Damage( dmg, damageType: DamageType.PoisonFinish, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 0f, EnemySource, EnemyType, damageFlags: damageFlags );
}
}
//SS2Game.PlaySfx( "poison_tick", Unit.Position2D, pitch: Game.Random.Float( 0.8f, 0.85f ), volume: 1.1f );
}
}
}
public override void StartDying( Player player )
{
base.StartDying( player );
if ( Game.Random.Float( 0f, 1f ) < DieSpreadChance )
{
float radius = 60f * RadiusMultiplier;
Manager.Instance.SpawnRingRpc( Unit.Position2D, radius, new Color( 0f, 0.4f, 0f, 0.95f ), Game.Random.Float( 0.3f, 0.4f ), path: "ring2" );
foreach ( var unit in Manager.Instance.GetNearbyUnits( Unit.Position2D, radius, except: Unit ) )
{
if ( !unit.IsInanimate )
unit.Poison( PlayerSource, EnemySource, EnemyType, Damage, FinishDamagePercent, DieSpreadChance, RadiusMultiplier, IsFlammable, TickTimeModifier, HitsToRemove );
}
}
}
public override void OnRemove( bool playEffects = true )
{
Unit.SetStatusPoison( false );
}
public override void Refresh()
{
base.Refresh();
}
}