aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut604
1 files changed, 604 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut
new file mode 100644
index 000000000..c2036e85d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut
@@ -0,0 +1,604 @@
+untyped
+
+global function Grenade_FileInit
+global function GetGrenadeThrowSound_1p
+global function GetGrenadeDeploySound_1p
+global function GetGrenadeThrowSound_3p
+global function GetGrenadeDeploySound_3p
+global function GetGrenadeProjectileSound
+
+const DEFAULT_FUSE_TIME = 2.25
+const DEFAULT_WARNING_TIME = 1.0
+global const float DEFAULT_MAX_COOK_TIME = 99999.9 //Longer than an entire day. Really just an arbitrarily large number
+
+global function Grenade_OnWeaponTossReleaseAnimEvent
+global function Grenade_OnWeaponTossCancelDrop
+global function Grenade_OnWeaponDeactivate
+global function Grenade_OnWeaponTossPrep
+global function Grenade_OnProjectileIgnite
+
+#if SERVER
+ global function Grenade_OnPlayerNPCTossGrenade_Common
+ global function ProxMine_Triggered
+ global function EnableTrapWarningSound
+ global function AddToProximityTargets
+ global function ProximityMineThink
+#endif
+global function Grenade_Init
+
+const GRENADE_EXPLOSIVE_WARNING_SFX_LOOP = "Weapon_Vortex_Gun.ExplosiveWarningBeep"
+const EMP_MAGNETIC_FORCE = 1600
+const MAG_FLIGHT_SFX_LOOP = "Explo_MGL_MagneticAttract"
+
+//Proximity Mine Settings
+global const PROXIMITY_MINE_EXPLOSION_DELAY = 1.2
+global const PROXIMITY_MINE_ARMING_DELAY = 1.0
+const TRIGGERED_ALARM_SFX = "Weapon_ProximityMine_CloseWarning"
+global const THERMITE_GRENADE_FX = $"P_grenade_thermite"
+global const CLUSTER_BASE_FX = $"P_wpn_meteor_exp"
+
+global const ProximityTargetClassnames = {
+ [ "npc_soldier_shield" ] = true,
+ [ "npc_soldier_heavy" ] = true,
+ [ "npc_soldier" ] = true,
+ [ "npc_spectre" ] = true,
+ [ "npc_drone" ] = true,
+ [ "npc_titan" ] = true,
+ [ "npc_marvin" ] = true,
+ [ "player" ] = true,
+ [ "npc_turret_mega" ] = true,
+ [ "npc_turret_sentry" ] = true,
+ [ "npc_dropship" ] = true,
+}
+
+const SOLDIER_ARC_STUN_ANIMS = [
+ "pt_react_ARC_fall",
+ "pt_react_ARC_kneefall",
+ "pt_react_ARC_sidefall",
+ "pt_react_ARC_slowfall",
+ "pt_react_ARC_scream",
+ "pt_react_ARC_stumble_F",
+ "pt_react_ARC_stumble_R" ]
+
+function Grenade_FileInit()
+{
+ PrecacheParticleSystem( CLUSTER_BASE_FX )
+
+ RegisterSignal( "ThrowGrenade" )
+ RegisterSignal( "WeaponDeactivateEvent" )
+ RegisterSignal( "OnEMPPilotHit" )
+ RegisterSignal( "StopGrenadeClientEffects" )
+ RegisterSignal( "DisableTrapWarningSound" )
+
+ //Globalize( MagneticFlight )
+
+ #if CLIENT
+ AddDestroyCallback( "grenade_frag", ClientDestroyCallback_GrenadeDestroyed )
+ #endif
+
+ #if SERVER
+ level._empForcedCallbacks <- {}
+ level._proximityTargetArrayID <- CreateScriptManagedEntArray()
+
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_proximity_mine, ProxMine_Triggered )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_thermite_grenade, Thermite_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_frag_grenade, Frag_DamagedPlayerOrNPC )
+
+ level._empForcedCallbacks[eDamageSourceId.mp_weapon_grenade_emp] <- true
+ level._empForcedCallbacks[eDamageSourceId.mp_weapon_proximity_mine] <- true
+
+ PrecacheParticleSystem( THERMITE_GRENADE_FX )
+ #endif
+}
+
+void function Grenade_OnWeaponTossPrep( entity weapon, WeaponTossPrepParams prepParams )
+{
+ weapon.w.startChargeTime = Time()
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+ weapon.EmitWeaponSound_1p3p( GetGrenadeDeploySound_1p( weapon ), GetGrenadeDeploySound_3p( weapon ) )
+
+ #if SERVER
+ thread HACK_CookGrenade( weapon, weaponOwner )
+ thread HACK_DropGrenadeOnDeath( weapon, weaponOwner )
+ #elseif CLIENT
+ if ( weaponOwner.IsPlayer() )
+ {
+ weaponOwner.p.grenadePulloutTime = Time()
+ }
+ #endif
+}
+
+void function Grenade_OnWeaponDeactivate( entity weapon )
+{
+ StopSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+ weapon.Signal( "WeaponDeactivateEvent" )
+}
+
+void function Grenade_OnProjectileIgnite( entity weapon )
+{
+ printt( "Grenade_OnProjectileIgnite() callback." )
+}
+
+function Grenade_Init( entity grenade, entity weapon )
+{
+ entity weaponOwner = weapon.GetOwner()
+ if ( IsValid( weaponOwner ) )
+ SetTeam( grenade, weaponOwner.GetTeam() )
+
+ // JFS: this is because I don't know if the above line should be
+ // weapon.GetOwner() or it's a typo and should really be weapon.GetWeaponOwner()
+ // and it's too close to ship and who knows what effect that will have
+ entity owner = weapon.GetWeaponOwner()
+ if ( IsMultiplayer() && IsValid( owner ) )
+ {
+ if ( owner.IsNPC() )
+ {
+ SetTeam( grenade, owner.GetTeam() )
+ }
+ }
+
+ #if SERVER
+ bool smartPistolVisible = weapon.GetWeaponSettingBool( eWeaponVar.projectile_visible_to_smart_ammo )
+ if ( smartPistolVisible )
+ {
+ grenade.SetDamageNotifications( true )
+ grenade.SetTakeDamageType( DAMAGE_EVENTS_ONLY )
+ grenade.proj.onlyAllowSmartPistolDamage = true
+
+ if ( !grenade.GetProjectileWeaponSettingBool( eWeaponVar.projectile_damages_owner ) && !grenade.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner ) )
+ SetCustomSmartAmmoTarget( grenade, true ) // prevent friendly target lockon
+ }
+ else
+ {
+ grenade.SetTakeDamageType( DAMAGE_NO )
+ }
+ #endif
+ if ( IsValid( weaponOwner ) )
+ grenade.s.originalOwner <- weaponOwner // for later in damage callbacks, to skip damage vs friendlies but not for og owner or his enemies
+}
+
+
+int function Grenade_OnWeaponToss_( entity weapon, WeaponPrimaryAttackParams attackParams, float directionScale )
+{
+ weapon.EmitWeaponSound_1p3p( GetGrenadeThrowSound_1p( weapon ), GetGrenadeThrowSound_3p( weapon ) )
+ bool projectilePredicted = PROJECTILE_PREDICTED
+ bool projectileLagCompensated = PROJECTILE_LAG_COMPENSATED
+#if SERVER
+ if ( weapon.IsForceReleaseFromServer() )
+ {
+ projectilePredicted = false
+ projectileLagCompensated = false
+ }
+#endif
+ entity grenade = Grenade_Launch( weapon, attackParams.pos, (attackParams.dir * directionScale), projectilePredicted, projectileLagCompensated )
+ entity weaponOwner = weapon.GetWeaponOwner()
+ weaponOwner.Signal( "ThrowGrenade" )
+
+ PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in Hack_DropGrenadeOnDeath - accurate for when cooldown actually begins
+
+#if SERVER
+
+ #if BATTLECHATTER_ENABLED
+ TryPlayWeaponBattleChatterLine( weaponOwner, weapon )
+ #endif
+
+#endif
+
+ return weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+}
+
+var function Grenade_OnWeaponTossReleaseAnimEvent( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ var result = Grenade_OnWeaponToss_( weapon, attackParams, 1.0 )
+ return result
+}
+
+var function Grenade_OnWeaponTossCancelDrop( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ var result = Grenade_OnWeaponToss_( weapon, attackParams, 0.2 )
+ return result
+}
+
+// Can return entity or nothing
+entity function Grenade_Launch( entity weapon, vector attackPos, vector throwVelocity, bool isPredicted, bool isLagCompensated )
+{
+ #if CLIENT
+ if ( !weapon.ShouldPredictProjectiles() )
+ return null
+ #endif
+
+ //TEMP FIX while Deploy anim is added to sprint
+ float currentTime = Time()
+ if ( weapon.w.startChargeTime == 0.0 )
+ weapon.w.startChargeTime = currentTime
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+
+ var discThrow = weapon.GetWeaponInfoFileKeyField( "grenade_disc_throw" )
+
+ vector angularVelocity = Vector( 3600, RandomFloatRange( -1200, 1200 ), 0 )
+
+ if ( discThrow == 1 )
+ angularVelocity = Vector( 100, 100, RandomFloatRange( 1200, 2200 ) )
+
+
+ float fuseTime
+
+ float baseFuseTime = weapon.GetGrenadeFuseTime() //Note that fuse time of 0 means the grenade won't explode on its own, instead it depends on OnProjectileCollision() functions to be defined and explode there. Arguably in this case grenade_fuse_time shouldn't be 0, but an arbitrarily large number instead.
+ if ( baseFuseTime > 0.0 )
+ {
+ fuseTime = baseFuseTime - ( currentTime - weapon.w.startChargeTime )
+ if ( fuseTime <= 0 )
+ fuseTime = 0.001
+ }
+ else
+ {
+ fuseTime = baseFuseTime
+ }
+
+ int damageFlags = weapon.GetWeaponDamageFlags()
+ entity frag = weapon.FireWeaponGrenade( attackPos, throwVelocity, angularVelocity, fuseTime, damageFlags, damageFlags, isPredicted, isLagCompensated, true )
+ if ( frag == null )
+ return null
+
+ if ( discThrow == 1 ) // add wobble by pitching it slightly
+ {
+ Assert( !frag.IsMarkedForDeletion(), "Frag before .SetAngles() is marked for deletion." )
+ frag.SetAngles( frag.GetAngles() + < RandomFloatRange( 7,11 ),0,0 > )
+ //Assert( !frag.IsMarkedForDeletion(), "Frag after .SetAngles() is marked for deletion." )
+ if ( frag.IsMarkedForDeletion() )
+ {
+ CodeWarning( "Frag after .SetAngles() was marked for deletion." )
+ return null
+ }
+ }
+
+ Grenade_OnPlayerNPCTossGrenade_Common( weapon, frag )
+
+ return frag
+}
+
+void function Grenade_OnPlayerNPCTossGrenade_Common( entity weapon, entity frag )
+{
+ Grenade_Init( frag, weapon )
+ #if SERVER
+ thread TrapExplodeOnDamage( frag, 20, 0.0, 0.0 )
+
+ string projectileSound = GetGrenadeProjectileSound( weapon )
+ if ( projectileSound != "" )
+ EmitSoundOnEntity( frag, projectileSound )
+ #endif
+
+ if( weapon.HasMod( "burn_mod_emp_grenade" ) )
+ frag.InitMagnetic( EMP_MAGNETIC_FORCE, MAG_FLIGHT_SFX_LOOP )
+}
+
+struct CookGrenadeStruct //Really just a convenience struct so we can read the changed value of a bool in an OnThreadEnd
+{
+ bool shouldOverrideFuseTime = false
+}
+
+void function HACK_CookGrenade( entity weapon, entity weaponOwner )
+{
+ float maxCookTime = GetMaxCookTime( weapon )
+ if ( maxCookTime >= DEFAULT_MAX_COOK_TIME )
+ return
+
+ weaponOwner.EndSignal( "OnDeath" )
+ weaponOwner.EndSignal( "ThrowGrenade" )
+ weapon.EndSignal( "WeaponDeactivateEvent" )
+ weapon.EndSignal( "OnDestroy" )
+
+ /*CookGrenadeStruct grenadeStruct
+
+ OnThreadEnd(
+ function() : ( weapon, grenadeStruct )
+ {
+ if ( grenadeStruct.shouldOverrideFuseTime )
+ {
+ var minFuseTime = weapon.GetWeaponInfoFileKeyField( "min_fuse_time" )
+ printt( "minFuseTime: " + minFuseTime )
+ if ( minFuseTime != null )
+ {
+ expect float( minFuseTime )
+ printt( "Setting overrideFuseTime to : " + weapon.GetWeaponInfoFileKeyField( "min_fuse_time" ) )
+ weapon.w.overrideFuseTime = minFuseTime
+ }
+ }
+ }
+ )
+*/
+ if ( maxCookTime - DEFAULT_WARNING_TIME <= 0 )
+ {
+ EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+ wait maxCookTime
+ }
+ else
+ {
+ wait( maxCookTime - DEFAULT_WARNING_TIME )
+
+ EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+
+ wait( DEFAULT_WARNING_TIME )
+ }
+
+ if ( !IsValid( weapon.GetWeaponOwner() ) )
+ return
+
+ weapon.ForceReleaseFromServer() // Will eventually result in Grenade_OnWeaponToss_() or equivalent function
+
+ // JFS: prevent grenade cook exploit in coliseum
+ if ( GameRules_GetGameMode() == COLISEUM )
+ {
+ #if SERVER
+ int damageSource = weapon.GetDamageSourceID()
+
+ if ( damageSource == eDamageSourceId.mp_weapon_frag_grenade )
+ {
+ var impact_effect_table = weapon.GetWeaponInfoFileKeyField( "impact_effect_table" )
+ if ( impact_effect_table != null )
+ {
+ string fx = expect string( impact_effect_table )
+ PlayImpactFXTable( weaponOwner.EyePosition(), weaponOwner, fx )
+ }
+ weaponOwner.Die( weaponOwner, weapon, { damageSourceId = damageSource } )
+ }
+ #endif
+ }
+
+ weaponOwner.Signal( "ThrowGrenade" ) // Only necessary to end HACK_DropGrenadeOnDeath
+}
+
+
+void function HACK_WaitForGrenadeDropEvent( weapon, entity weaponOwner )
+{
+ weapon.EndSignal( "WeaponDeactivateEvent" )
+
+ weaponOwner.WaitSignal( "OnDeath" )
+}
+
+
+void function HACK_DropGrenadeOnDeath( entity weapon, entity weaponOwner )
+{
+ if ( weapon.HasMod( "burn_card_weapon_mod" ) ) //JFS: Primarily to stop boost grenade weapons (e.g. frag_drone ) not doing TryUsingBurnCardWeapon() when dropped through this function.
+ return
+
+ weaponOwner.EndSignal( "ThrowGrenade" )
+ weaponOwner.EndSignal( "OnDestroy" )
+
+ waitthread HACK_WaitForGrenadeDropEvent( weapon, weaponOwner )
+
+ if( !IsValid( weaponOwner ) || !IsValid( weapon ) || IsAlive( weaponOwner ) )
+ return
+
+ float elapsedTime = Time() - weapon.w.startChargeTime
+ float baseFuseTime = weapon.GetGrenadeFuseTime()
+ float fuseDelta = (baseFuseTime - elapsedTime)
+
+ if ( (baseFuseTime == 0.0) || (fuseDelta > -0.1) )
+ {
+ float forwardScale = weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_scale )
+ vector velocity = weaponOwner.GetForwardVector() * forwardScale
+ velocity.z += weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_extraUp )
+ vector angularVelocity = Vector( 0, 0, 0 )
+ float fuseTime = baseFuseTime ? baseFuseTime - elapsedTime : baseFuseTime
+
+ int primaryClipCount = weapon.GetWeaponPrimaryClipCount()
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ weapon.SetWeaponPrimaryClipCountAbsolute( maxint( 0, primaryClipCount - ammoPerShot ) )
+
+ PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in ReleaseAnimEvent - for cases where grenade is dropped on death
+
+ entity grenade = Grenade_Launch( weapon, weaponOwner.GetOrigin(), velocity, PROJECTILE_NOT_PREDICTED, PROJECTILE_NOT_LAG_COMPENSATED )
+ }
+}
+
+
+#if SERVER
+void function ProxMine_Triggered( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( attacker ) )
+ return
+
+ if ( attacker == ent )
+ return
+
+ if ( ent.IsPlayer() || ent.IsNPC() )
+ thread ShowProxMineTriggeredIcon( ent )
+
+ //If this feature is good, we should add this to NPCs as well. Currently script errors if applied to an NPC.
+ //if ( ent.IsPlayer() )
+ // thread ProxMine_ShowOnMinimapTimed( ent, GetOtherTeam( ent.GetTeam() ), PROX_MINE_MARKER_TIME )
+}
+
+/*
+function ProxMine_ShowOnMinimapTimed( ent, teamToDisplayEntTo, duration )
+{
+ ent.Minimap_AlwaysShow( teamToDisplayEntTo, null )
+ Minimap_CreatePingForTeam( teamToDisplayEntTo, ent.GetOrigin(), $"vgui/HUD/titanFiringPing", 1.0 )
+
+ wait duration
+
+ if ( IsValid( ent ) && ent.IsPlayer() )
+ ent.Minimap_DisplayDefault( teamToDisplayEntTo, ent )
+}
+*/
+
+void function Thermite_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ Thermite_DamagePlayerOrNPCSounds( ent )
+}
+
+void function Frag_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ #if MP
+ if ( !IsValid( ent ) || ent.IsPlayer() || ent.IsTitan() )
+ return
+
+ if ( ent.IsMechanical() )
+ DamageInfo_ScaleDamage( damageInfo, 0.5 )
+ #endif
+}
+
+#endif // SERVER
+
+
+#if CLIENT
+void function ClientDestroyCallback_GrenadeDestroyed( entity grenade )
+{
+}
+#endif // CLIENT
+
+#if SERVER
+function EnableTrapWarningSound( entity trap, delay = 0, warningSound = DEFAULT_WARNING_SFX )
+{
+ trap.EndSignal( "OnDestroy" )
+ trap.EndSignal( "DisableTrapWarningSound" )
+
+ if ( delay > 0 )
+ wait delay
+
+ while ( IsValid( trap ) )
+ {
+ EmitSoundOnEntity( trap, warningSound )
+ wait 1.0
+ }
+}
+
+void function AddToProximityTargets( entity ent )
+{
+ AddToScriptManagedEntArray( level._proximityTargetArrayID, ent );
+}
+
+function ProximityMineThink( entity proximityMine, entity owner )
+{
+ proximityMine.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( proximityMine )
+ {
+ if ( IsValid( proximityMine ) )
+ proximityMine.Destroy()
+ }
+ )
+ thread TrapExplodeOnDamage( proximityMine, 50 )
+
+ wait PROXIMITY_MINE_ARMING_DELAY
+
+ int teamNum = proximityMine.GetTeam()
+ float explodeRadius = proximityMine.GetDamageRadius()
+ float triggerRadius = ( ( explodeRadius * 0.75 ) + 0.5 )
+ local lastTimeNPCsChecked = 0
+ local NPCTickRate = 0.5
+ local PlayerTickRate = 0.2
+
+ // Wait for someone to enter proximity
+ while( IsValid( proximityMine ) && IsValid( owner ) )
+ {
+ if ( lastTimeNPCsChecked + NPCTickRate <= Time() )
+ {
+ array<entity> nearbyNPCs = GetNPCArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius )
+ foreach( ent in nearbyNPCs )
+ {
+ if ( ShouldSetOffProximityMine( proximityMine, ent ) )
+ {
+ ProximityMine_Explode( proximityMine )
+ return
+ }
+ }
+ lastTimeNPCsChecked = Time()
+ }
+
+ array<entity> nearbyPlayers = GetPlayerArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius )
+ foreach( ent in nearbyPlayers )
+ {
+ if ( ShouldSetOffProximityMine( proximityMine, ent ) )
+ {
+ ProximityMine_Explode( proximityMine )
+ return
+ }
+ }
+
+ wait PlayerTickRate
+ }
+}
+
+function ProximityMine_Explode( proximityMine )
+{
+ local explodeTime = Time() + PROXIMITY_MINE_EXPLOSION_DELAY
+ EmitSoundOnEntity( proximityMine, TRIGGERED_ALARM_SFX )
+
+ wait PROXIMITY_MINE_EXPLOSION_DELAY
+
+ if ( IsValid( proximityMine ) )
+ proximityMine.GrenadeExplode( proximityMine.GetForwardVector() )
+}
+
+bool function ShouldSetOffProximityMine( entity proximityMine, entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ent.IsPhaseShifted() )
+ return false
+
+ TraceResults results = TraceLine( proximityMine.GetOrigin(), ent.EyePosition(), proximityMine, (TRACE_MASK_SHOT | CONTENTS_BLOCKLOS), TRACE_COLLISION_GROUP_NONE )
+ if ( results.fraction >= 1 || results.hitEnt == ent )
+ return true
+
+ return false
+}
+
+#endif // SERVER
+
+
+
+float function GetMaxCookTime( entity weapon )
+{
+ var cookTime = weapon.GetWeaponInfoFileKeyField( "max_cook_time" )
+ if (cookTime == null )
+ return DEFAULT_MAX_COOK_TIME
+
+ expect float ( cookTime )
+ return cookTime
+}
+
+function GetGrenadeThrowSound_1p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) : ""
+}
+
+
+function GetGrenadeDeploySound_1p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) : ""
+}
+
+
+function GetGrenadeThrowSound_3p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) : ""
+}
+
+
+function GetGrenadeDeploySound_3p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) : ""
+}
+
+string function GetGrenadeProjectileSound( weapon )
+{
+ return expect string( weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) ? weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) : "" )
+}