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