2021-11-29 02:34:44 +13:00
using Content.Server.Administration.Logs ;
2021-10-25 16:21:56 +02:00
using Content.Server.NodeContainer ;
using Content.Server.NodeContainer.EntitySystems ;
using Content.Server.NodeContainer.NodeGroups ;
using Content.Server.NodeContainer.Nodes ;
using Content.Server.Power.Components ;
using Content.Server.Power.EntitySystems ;
using Content.Server.Power.NodeGroups ;
using Content.Shared.Damage ;
using Content.Shared.Damage.Prototypes ;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database ;
2021-10-25 16:21:56 +02:00
using Content.Shared.Electrocution ;
using Content.Shared.Interaction ;
2022-09-06 04:48:35 +02:00
using Content.Shared.Inventory ;
2021-10-25 16:21:56 +02:00
using Content.Shared.Jittering ;
using Content.Shared.Maps ;
using Content.Shared.Popups ;
using Content.Shared.Pulling.Components ;
using Content.Shared.Speech.EntitySystems ;
using Content.Shared.StatusEffect ;
using Content.Shared.Stunnable ;
2022-02-13 21:20:47 -06:00
using Content.Shared.Tag ;
2021-10-25 16:21:56 +02:00
using Content.Shared.Weapons.Melee ;
2022-09-29 15:51:59 +10:00
using Content.Shared.Weapons.Melee.Events ;
2022-05-09 18:40:15 -07:00
using Robust.Shared.Audio ;
2021-10-25 16:21:56 +02:00
using Robust.Shared.Physics.Dynamics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Events ;
2021-10-25 16:21:56 +02:00
using Robust.Shared.Player ;
using Robust.Shared.Prototypes ;
using Robust.Shared.Random ;
using Robust.Shared.Utility ;
namespace Content.Server.Electrocution
{
public sealed class ElectrocutionSystem : SharedElectrocutionSystem
{
2022-03-03 21:18:35 +11:00
[Dependency] private readonly EntityLookupSystem _entityLookup = default ! ;
2021-10-25 16:21:56 +02:00
[Dependency] private readonly IRobustRandom _random = default ! ;
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default ! ;
[Dependency] private readonly SharedJitteringSystem _jitteringSystem = default ! ;
[Dependency] private readonly SharedStunSystem _stunSystem = default ! ;
[Dependency] private readonly SharedStutteringSystem _stutteringSystem = default ! ;
[Dependency] private readonly SharedPopupSystem _popupSystem = default ! ;
[Dependency] private readonly DamageableSystem _damageableSystem = default ! ;
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-03-07 18:14:55 +13:00
[Dependency] private readonly TagSystem _tagSystem = default ! ;
2021-10-25 16:21:56 +02:00
2021-10-27 18:10:40 +02:00
private const string StatusEffectKey = "Electrocution" ;
private const string DamageType = "Shock" ;
2021-10-25 16:21:56 +02:00
// Yes, this is absurdly small for a reason.
private const float ElectrifiedDamagePerWatt = 0.0015f ;
private const float RecursiveDamageMultiplier = 0.75f ;
private const float RecursiveTimeMultiplier = 0.8f ;
private const float ParalyzeTimeMultiplier = 1f ;
private const float StutteringTimeMultiplier = 1.5f ;
private const float JitterTimeMultiplier = 0.75f ;
private const float JitterAmplitude = 80f ;
private const float JitterFrequency = 8f ;
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < ElectrifiedComponent , StartCollideEvent > ( OnElectrifiedStartCollide ) ;
SubscribeLocalEvent < ElectrifiedComponent , AttackedEvent > ( OnElectrifiedAttacked ) ;
SubscribeLocalEvent < ElectrifiedComponent , InteractHandEvent > ( OnElectrifiedHandInteract ) ;
2021-12-04 01:59:09 -08:00
SubscribeLocalEvent < ElectrifiedComponent , InteractUsingEvent > ( OnElectrifiedInteractUsing ) ;
2021-10-25 16:21:56 +02:00
SubscribeLocalEvent < RandomInsulationComponent , MapInitEvent > ( OnRandomInsulationMapInit ) ;
UpdatesAfter . Add ( typeof ( PowerNetSystem ) ) ;
}
public override void Update ( float frameTime )
{
// Update "in progress" electrocutions
RemQueue < ElectrocutionComponent > finishedElectrocutionsQueue = new ( ) ;
foreach ( var ( electrocution , consumer ) in EntityManager
. EntityQuery < ElectrocutionComponent , PowerConsumerComponent > ( ) )
{
var ftAdjusted = Math . Min ( frameTime , electrocution . TimeLeft ) ;
electrocution . TimeLeft - = ftAdjusted ;
electrocution . AccumulatedDamage + = consumer . ReceivedPower * ElectrifiedDamagePerWatt * ftAdjusted ;
if ( MathHelper . CloseTo ( electrocution . TimeLeft , 0 ) )
finishedElectrocutionsQueue . Add ( electrocution ) ;
}
foreach ( var finished in finishedElectrocutionsQueue )
{
2021-12-05 18:09:01 +01:00
var uid = finished . Owner ;
2021-10-25 16:21:56 +02:00
if ( EntityManager . EntityExists ( finished . Electrocuting ) )
{
// TODO: damage should be scaled by shock damage multiplier
// TODO: better paralyze/jitter timing
var damage = new DamageSpecifier (
_prototypeManager . Index < DamageTypePrototype > ( DamageType ) ,
( int ) finished . AccumulatedDamage ) ;
2022-10-08 12:15:27 +02:00
var actual = _damageableSystem . TryChangeDamage ( finished . Electrocuting , damage , origin : finished . Source ) ;
2021-11-29 02:34:44 +13:00
if ( actual ! = null )
2022-01-05 00:19:23 -08:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Electrocution ,
2022-12-15 12:30:47 -06:00
$"{ToPrettyString(finished.Electrocuting):entity} received {actual.Total:damage} powered electrocution damage from {ToPrettyString(finished.Source):source}" ) ;
2022-01-05 00:19:23 -08:00
}
2021-10-25 16:21:56 +02:00
}
EntityManager . DeleteEntity ( uid ) ;
}
}
2022-09-14 17:26:26 +10:00
private void OnElectrifiedStartCollide ( EntityUid uid , ElectrifiedComponent electrified , ref StartCollideEvent args )
2021-10-25 16:21:56 +02:00
{
if ( ! electrified . OnBump )
return ;
2022-01-15 15:34:43 +13:00
TryDoElectrifiedAct ( uid , args . OtherFixture . Body . Owner , 1 , electrified ) ;
2021-10-25 16:21:56 +02:00
}
private void OnElectrifiedAttacked ( EntityUid uid , ElectrifiedComponent electrified , AttackedEvent args )
{
if ( ! electrified . OnAttacked )
return ;
2022-01-15 15:34:43 +13:00
TryDoElectrifiedAct ( uid , args . User , 1 , electrified ) ;
2021-10-25 16:21:56 +02:00
}
private void OnElectrifiedHandInteract ( EntityUid uid , ElectrifiedComponent electrified , InteractHandEvent args )
{
if ( ! electrified . OnHandInteract )
return ;
2022-01-15 15:34:43 +13:00
TryDoElectrifiedAct ( uid , args . User , 1 , electrified ) ;
2021-10-25 16:21:56 +02:00
}
2021-12-04 01:59:09 -08:00
private void OnElectrifiedInteractUsing ( EntityUid uid , ElectrifiedComponent electrified , InteractUsingEvent args )
{
if ( ! electrified . OnInteractUsing )
return ;
2022-01-15 15:34:43 +13:00
var siemens = TryComp ( args . Used , out InsulatedComponent ? insulation )
? insulation . SiemensCoefficient
: 1 ;
TryDoElectrifiedAct ( uid , args . User , siemens , electrified ) ;
2021-12-04 01:59:09 -08:00
}
2021-10-25 16:21:56 +02:00
public bool TryDoElectrifiedAct ( EntityUid uid , EntityUid targetUid ,
2022-01-15 15:34:43 +13:00
float siemens = 1 ,
2021-10-25 16:21:56 +02:00
ElectrifiedComponent ? electrified = null ,
NodeContainerComponent ? nodeContainer = null ,
2021-11-08 12:37:32 +01:00
TransformComponent ? transform = null )
2021-10-25 16:21:56 +02:00
{
if ( ! Resolve ( uid , ref electrified , ref transform , false ) )
return false ;
if ( ! electrified . Enabled )
return false ;
if ( electrified . NoWindowInTile )
{
foreach ( var entity in transform . Coordinates . GetEntitiesInTile (
2022-12-15 17:29:46 +13:00
LookupFlags . Approximate | LookupFlags . Static , _entityLookup ) )
2021-10-25 16:21:56 +02:00
{
2022-03-07 18:14:55 +13:00
if ( _tagSystem . HasTag ( entity , "Window" ) )
2021-10-25 16:21:56 +02:00
return false ;
}
}
2022-01-15 15:34:43 +13:00
siemens * = electrified . SiemensCoefficient ;
2021-10-28 11:50:31 +02:00
if ( ! DoCommonElectrocutionAttempt ( targetUid , uid , ref siemens ) | | siemens < = 0 )
return false ; // If electrocution would fail, do nothing.
2021-10-25 16:21:56 +02:00
var targets = new List < ( EntityUid entity , int depth ) > ( ) ;
GetChainedElectrocutionTargets ( targetUid , targets ) ;
2022-04-15 14:21:11 -07:00
if ( ! electrified . RequirePower | | electrified . UsesApcPower )
2021-10-25 16:21:56 +02:00
{
2022-04-15 14:21:11 -07:00
// Does it use APC power for its electrification check? Check if it's powered, and then
// attempt an electrocution if all the checks succeed.
2022-05-27 10:36:12 +10:00
if ( electrified . UsesApcPower & & ! this . IsPowered ( uid , EntityManager ) )
2022-04-15 14:21:11 -07:00
{
return false ;
}
2021-10-25 16:21:56 +02:00
var lastRet = true ;
for ( var i = targets . Count - 1 ; i > = 0 ; i - - )
{
var ( entity , depth ) = targets [ i ] ;
lastRet = TryDoElectrocution (
entity ,
uid ,
( int ) ( electrified . ShockDamage * MathF . Pow ( RecursiveDamageMultiplier , depth ) ) ,
2021-12-07 09:18:07 +03:00
TimeSpan . FromSeconds ( electrified . ShockTime * MathF . Pow ( RecursiveTimeMultiplier , depth ) ) , true ,
2021-10-25 16:21:56 +02:00
electrified . SiemensCoefficient ) ;
}
return lastRet ;
}
if ( ! Resolve ( uid , ref nodeContainer , false ) )
return false ;
var node = TryNode ( electrified . HighVoltageNode ) ? ?
TryNode ( electrified . MediumVoltageNode ) ? ?
TryNode ( electrified . LowVoltageNode ) ;
if ( node = = null )
return false ;
var ( damageMult , timeMult ) = node . NodeGroupID switch
{
NodeGroupID . HVPower = > ( electrified . HighVoltageDamageMultiplier , electrified . HighVoltageTimeMultiplier ) ,
NodeGroupID . MVPower = > ( electrified . MediumVoltageDamageMultiplier ,
electrified . MediumVoltageTimeMultiplier ) ,
_ = > ( 1f , 1f )
} ;
{
var lastRet = true ;
for ( var i = targets . Count - 1 ; i > = 0 ; i - - )
{
var ( entity , depth ) = targets [ i ] ;
lastRet = TryDoElectrocutionPowered (
entity ,
uid ,
node ,
( int ) ( electrified . ShockDamage * MathF . Pow ( RecursiveDamageMultiplier , depth ) * damageMult ) ,
TimeSpan . FromSeconds ( electrified . ShockTime * MathF . Pow ( RecursiveTimeMultiplier , depth ) *
2021-12-07 09:18:07 +03:00
timeMult ) , true ,
2021-10-25 16:21:56 +02:00
electrified . SiemensCoefficient ) ;
}
return lastRet ;
}
Node ? TryNode ( string? id )
{
2022-01-05 00:19:23 -08:00
if ( id ! = null & & nodeContainer . TryGetNode < Node > ( id , out var tryNode )
2022-11-09 14:43:45 +13:00
& & tryNode . NodeGroup is IBasePowerNet { NetworkNode : { LastCombinedSupply : > 0 } } )
2021-10-25 16:21:56 +02:00
{
2022-01-05 00:19:23 -08:00
return tryNode ;
2021-10-25 16:21:56 +02:00
}
return null ;
}
}
2022-09-06 04:48:35 +02:00
/// <param name="uid">Entity being electrocuted.</param>
/// <param name="sourceUid">Source entity of the electrocution.</param>
/// <param name="shockDamage">How much shock damage the entity takes.</param>
/// <param name="time">How long the entity will be stunned.</param>
/// <param name="refresh">Should <paramref>time</paramref> be refreshed (instead of accumilated) if the entity is already electrocuted?</param>
2023-01-19 03:56:45 +01:00
/// <param name="siemensCoefficient">How insulated the entity is from the shock. 0 means completely insulated, and 1 means no insulation.</param>
/// <param name="statusEffects">Status effects to apply to the entity.</param>
2022-09-06 04:48:35 +02:00
/// <param name="ignoreInsulation">Should the electrocution bypass the Insulated component?</param>
2021-10-25 16:21:56 +02:00
/// <returns>Whether the entity <see cref="uid"/> was stunned by the shock.</returns>
public bool TryDoElectrocution (
2021-12-07 09:18:07 +03:00
EntityUid uid , EntityUid ? sourceUid , int shockDamage , TimeSpan time , bool refresh , float siemensCoefficient = 1f ,
2022-09-06 04:48:35 +02:00
StatusEffectsComponent ? statusEffects = null , bool ignoreInsulation = false )
2021-10-25 16:21:56 +02:00
{
2022-09-06 04:48:35 +02:00
if ( ! DoCommonElectrocutionAttempt ( uid , sourceUid , ref siemensCoefficient , ignoreInsulation )
2022-01-05 00:19:23 -08:00
| | ! DoCommonElectrocution ( uid , sourceUid , shockDamage , time , refresh , siemensCoefficient , statusEffects ) )
2021-10-25 16:21:56 +02:00
return false ;
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( uid , new ElectrocutedEvent ( uid , sourceUid , siemensCoefficient ) , true ) ;
2021-10-25 16:21:56 +02:00
return true ;
}
private bool TryDoElectrocutionPowered (
EntityUid uid ,
EntityUid sourceUid ,
Node node ,
int shockDamage ,
TimeSpan time ,
2021-12-07 09:18:07 +03:00
bool refresh ,
2021-10-25 16:21:56 +02:00
float siemensCoefficient = 1f ,
StatusEffectsComponent ? statusEffects = null ,
2021-11-08 12:37:32 +01:00
TransformComponent ? sourceTransform = null )
2021-10-25 16:21:56 +02:00
{
if ( ! DoCommonElectrocutionAttempt ( uid , sourceUid , ref siemensCoefficient ) )
return false ;
// Coefficient needs to be higher than this to do a powered electrocution!
2022-09-06 04:48:35 +02:00
if ( siemensCoefficient < = 0.5f )
2022-01-05 00:19:23 -08:00
return DoCommonElectrocution ( uid , sourceUid , shockDamage , time , refresh , siemensCoefficient , statusEffects ) ;
2021-10-25 16:21:56 +02:00
2022-01-05 00:19:23 -08:00
if ( ! DoCommonElectrocution ( uid , sourceUid , null , time , refresh , siemensCoefficient , statusEffects ) )
2021-10-25 16:21:56 +02:00
return false ;
if ( ! Resolve ( sourceUid , ref sourceTransform ) ) // This shouldn't really happen, but just in case...
return true ;
var electrocutionEntity = EntityManager . SpawnEntity (
$"VirtualElectrocutionLoad{node.NodeGroupID}" , sourceTransform . Coordinates ) ;
2021-12-08 13:00:43 +01:00
var electrocutionNode = EntityManager . GetComponent < NodeContainerComponent > ( electrocutionEntity )
2021-10-25 16:21:56 +02:00
. GetNode < ElectrocutionNode > ( "electrocution" ) ;
2021-12-08 13:00:43 +01:00
var electrocutionComponent = EntityManager . GetComponent < ElectrocutionComponent > ( electrocutionEntity ) ;
2021-10-25 16:21:56 +02:00
electrocutionNode . CableEntity = sourceUid ;
electrocutionNode . NodeName = node . Name ;
_nodeGroupSystem . QueueReflood ( electrocutionNode ) ;
electrocutionComponent . TimeLeft = 1f ;
electrocutionComponent . Electrocuting = uid ;
2022-10-08 12:15:27 +02:00
electrocutionComponent . Source = sourceUid ;
2021-10-25 16:21:56 +02:00
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( uid , new ElectrocutedEvent ( uid , sourceUid , siemensCoefficient ) , true ) ;
2021-10-25 16:21:56 +02:00
return true ;
}
2022-09-06 04:48:35 +02:00
private bool DoCommonElectrocutionAttempt ( EntityUid uid , EntityUid ? sourceUid , ref float siemensCoefficient , bool ignoreInsulation = false )
2021-10-25 16:21:56 +02:00
{
2022-09-06 04:48:35 +02:00
var attemptEvent = new ElectrocutionAttemptEvent ( uid , sourceUid , siemensCoefficient ,
ignoreInsulation ? SlotFlags . NONE : ~ SlotFlags . POCKET ) ;
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( uid , attemptEvent , true ) ;
2021-10-25 16:21:56 +02:00
// Cancel the electrocution early, so we don't recursively electrocute anything.
if ( attemptEvent . Cancelled )
return false ;
siemensCoefficient = attemptEvent . SiemensCoefficient ;
return true ;
}
private bool DoCommonElectrocution ( EntityUid uid , EntityUid ? sourceUid ,
2021-12-07 09:18:07 +03:00
int? shockDamage , TimeSpan time , bool refresh , float siemensCoefficient = 1f ,
2022-01-05 00:19:23 -08:00
StatusEffectsComponent ? statusEffects = null )
2021-10-25 16:21:56 +02:00
{
if ( siemensCoefficient < = 0 )
return false ;
if ( shockDamage ! = null )
{
shockDamage = ( int ) ( shockDamage * siemensCoefficient ) ;
if ( shockDamage . Value < = 0 )
return false ;
}
if ( ! Resolve ( uid , ref statusEffects , false ) | |
! _statusEffectsSystem . CanApplyEffect ( uid , StatusEffectKey , statusEffects ) )
return false ;
2021-12-07 09:18:07 +03:00
if ( ! _statusEffectsSystem . TryAddStatusEffect < ElectrocutedComponent > ( uid , StatusEffectKey , time , refresh ,
2022-01-05 00:19:23 -08:00
statusEffects ) )
2021-10-25 16:21:56 +02:00
return false ;
var shouldStun = siemensCoefficient > 0.5f ;
if ( shouldStun )
2022-01-05 00:19:23 -08:00
_stunSystem . TryParalyze ( uid , time * ParalyzeTimeMultiplier , refresh , statusEffects ) ;
2021-10-25 16:21:56 +02:00
// TODO: Sparks here.
if ( shockDamage is { } dmg )
2021-11-29 02:34:44 +13:00
{
var actual = _damageableSystem . TryChangeDamage ( uid ,
2022-10-08 12:15:27 +02:00
new DamageSpecifier ( _prototypeManager . Index < DamageTypePrototype > ( DamageType ) , dmg ) , origin : sourceUid ) ;
2021-10-25 16:21:56 +02:00
2021-11-29 02:34:44 +13:00
if ( actual ! = null )
2022-01-05 00:19:23 -08:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Electrocution ,
2022-12-15 12:30:47 -06:00
$"{ToPrettyString(statusEffects.Owner):entity} received {actual.Total:damage} powered electrocution damage{(sourceUid != null ? " from " + ToPrettyString(sourceUid.Value) : " "):source}" ) ;
2022-01-05 00:19:23 -08:00
}
2021-11-29 02:34:44 +13:00
}
2022-01-05 00:19:23 -08:00
_stutteringSystem . DoStutter ( uid , time * StutteringTimeMultiplier , refresh , statusEffects ) ;
2021-12-07 09:18:07 +03:00
_jitteringSystem . DoJitter ( uid , time * JitterTimeMultiplier , refresh , JitterAmplitude , JitterFrequency , true ,
2022-01-05 00:19:23 -08:00
statusEffects ) ;
2021-10-25 16:21:56 +02:00
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "electrocuted-component-mob-shocked-popup-player" ) , uid , uid ) ;
2021-10-25 16:21:56 +02:00
2022-12-19 10:41:47 +13:00
var filter = Filter . PvsExcept ( uid , entityManager : EntityManager ) ;
2021-10-25 16:21:56 +02:00
// TODO: Allow being able to pass EntityUid to Loc...
if ( sourceUid ! = null )
{
_popupSystem . PopupEntity ( Loc . GetString ( "electrocuted-component-mob-shocked-by-source-popup-others" ,
2022-12-19 10:41:47 +13:00
( "mob" , uid ) , ( "source" , ( sourceUid . Value ) ) ) , uid , filter , true ) ;
2022-05-09 18:40:15 -07:00
PlayElectrocutionSound ( uid , sourceUid . Value ) ;
2021-10-25 16:21:56 +02:00
}
else
{
_popupSystem . PopupEntity ( Loc . GetString ( "electrocuted-component-mob-shocked-popup-others" ,
2022-12-19 10:41:47 +13:00
( "mob" , uid ) ) , uid , filter , true ) ;
2021-10-25 16:21:56 +02:00
}
return true ;
}
private void GetChainedElectrocutionTargets ( EntityUid source , List < ( EntityUid entity , int depth ) > all )
{
var visited = new HashSet < EntityUid > ( ) ;
GetChainedElectrocutionTargetsRecurse ( source , 1 , visited , all ) ;
}
private void GetChainedElectrocutionTargetsRecurse (
EntityUid entity ,
int depth ,
HashSet < EntityUid > visited ,
List < ( EntityUid entity , int depth ) > all )
{
all . Add ( ( entity , depth ) ) ;
visited . Add ( entity ) ;
if ( EntityManager . TryGetComponent ( entity , out SharedPullableComponent ? pullable )
2021-12-05 21:02:04 +01:00
& & pullable . Puller is { Valid : true } pullerId
& & ! visited . Contains ( pullerId ) )
2021-10-25 16:21:56 +02:00
{
2021-12-05 21:02:04 +01:00
GetChainedElectrocutionTargetsRecurse ( pullerId , depth + 1 , visited , all ) ;
2021-10-25 16:21:56 +02:00
}
if ( EntityManager . TryGetComponent ( entity , out SharedPullerComponent ? puller )
2021-12-05 21:02:04 +01:00
& & puller . Pulling is { Valid : true } pullingId
& & ! visited . Contains ( pullingId ) )
2021-10-25 16:21:56 +02:00
{
2021-12-05 21:02:04 +01:00
GetChainedElectrocutionTargetsRecurse ( pullingId , depth + 1 , visited , all ) ;
2021-10-25 16:21:56 +02:00
}
}
private void OnRandomInsulationMapInit ( EntityUid uid , RandomInsulationComponent randomInsulation ,
MapInitEvent args )
{
if ( ! EntityManager . TryGetComponent ( uid , out InsulatedComponent ? insulated ) )
return ;
if ( randomInsulation . List . Length = = 0 )
return ;
SetInsulatedSiemensCoefficient ( uid , _random . Pick ( randomInsulation . List ) , insulated ) ;
}
2022-05-09 18:40:15 -07:00
private void PlayElectrocutionSound ( EntityUid targetUid , EntityUid sourceUid , ElectrifiedComponent ? electrified = null )
{
if ( ! Resolve ( sourceUid , ref electrified ) | | ! electrified . PlaySoundOnShock )
{
return ;
}
2022-06-12 19:45:47 -04:00
SoundSystem . Play ( electrified . ShockNoises . GetSound ( ) , Filter . Pvs ( targetUid ) , targetUid , AudioParams . Default . WithVolume ( electrified . ShockVolume ) ) ;
2022-05-09 18:40:15 -07:00
}
2021-10-25 16:21:56 +02:00
}
}