unitStatus/UnitStatusShock.cs

A UnitStatus subclass that applies a shock effect to a Unit. It deals damage after a short delay, can spread the shock to nearby units once, and tracks spread count, sources, and timing.

Networking
using System;
using Sandbox;
using Sandbox.Physics;

public class UnitStatusShock : UnitStatus
{
	private bool _hasDamaged;
	public static float DAMAGE_TIME = 0.1f;

	public static float SHOCK_TIME = 0.2f;
	private const float SHOCK_RANGE = 40f;

	public float Damage { get; set; }
	private bool _hasShocked;

	public int CurrSpreadCount { get; set; }
	public int SpreadLimit { get; set; }

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

	public UnitStatusShock()
	{

	}

	public override void Init( Unit unit )
	{
		base.Init( unit );

		Unit.SetStateShocked( true );

		Lifetime = 1f;
	}

	public override void Update( float dt )
	{
		base.Update( dt );

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

		if ( !Unit.IsValid() )
			return;

		if ( !_hasDamaged && ElapsedTime > DAMAGE_TIME )
		{
			DamageUnit();
			_hasDamaged = true;
		}

		if ( !_hasShocked && ElapsedTime > SHOCK_TIME && CurrSpreadCount < SpreadLimit )
		{
			TryShock();
			_hasShocked = true;
		}

		if ( ElapsedTime > Lifetime )
			Unit.RemoveUnitStatus( this );
	}

	void DamageUnit()
	{
		Manager.Instance.PlaySfxNearbyRpc( "zap", Unit.Position2D, pitch: Utils.Map( CurrSpreadCount, 0, SpreadLimit, 1.25f, 1.6f ) * Sandbox.Game.Random.Float( 0.9f, 1.1f ), volume: 1f, maxDist: 350f );

		if ( IsOnEnemy )
		{
			if ( Enemy.IsValid() )
				Enemy.DamageRpc( Damage, PlayerSource, DamageType.Shock, Enemy.WorldPosition.WithZ( 30f ), Vector2.Zero, isCrit: false, shouldFlinch: true );
		}
		else
		{
			if ( Player.IsValid() )
			{
				var isSelfInflicted = Player == PlayerSource;

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

				Player.Damage( Damage, damageType: DamageType.Shock, Player.Position2D, dir: Utils.GetRandomVector(), upwardAmount: Game.Random.Float( 0f, 0.5f ), force: 0f, ragdollForce: 1f, EnemySource, enemyType: EnemySource.IsValid() ? EnemySource.EnemyType : EnemyType.None, damageFlags: damageFlags );
			}
		}
	}

	void TryShock()
	{
		Unit closestUnit = Manager.Instance.GetClosestUnit( Unit.Position2D, Unit.Radius + SHOCK_RANGE, includeUnitRadius: true, except: Unit, condition: x => !x.IsShocked && !(x is Player player && player.IsInvincible), playerDistanceMult: 1.35f );
		if ( closestUnit.IsValid() )
			closestUnit.Shock( PlayerSource, EnemySource, Damage, CurrSpreadCount + 1, SpreadLimit );
	}

	public override void Die( Player player )
	{
		base.Die( player );

		if ( !_hasShocked && ElapsedTime < SHOCK_TIME && CurrSpreadCount < SpreadLimit )
			TryShock();
	}

	public override void OnRemove( bool playEffects = true )
	{
		Unit.SetStateShocked( false );
	}

	public override void Refresh()
	{
		base.Refresh();

	}
}