@@ -19,6 +19,9 @@ namespace Exiled.API.Extensions
1919 using AudioPooling ;
2020 using Cassie ;
2121 using CustomPlayerEffects ;
22+
23+ using Decals ;
24+
2225 using Exiled . API . Enums ;
2326 using Exiled . API . Features . Items ;
2427 using Exiled . API . Features . Items . Keycards ;
@@ -47,6 +50,8 @@ namespace Exiled.API.Extensions
4750 using UnityEngine ;
4851 using Utils . Networking ;
4952
53+ using Firearm = Features . Items . Firearm ;
54+
5055 /// <summary>
5156 /// A set of extensions for <see cref="Mirror"/> Networking.
5257 /// </summary>
@@ -220,43 +225,242 @@ public static void PlayGunSound(this Player player, Vector3 position, ItemType i
220225 /// <param name="firearmType">Weapon's sound to play.</param>
221226 /// <param name="pitch">Speed of sound.</param>
222227 /// <param name="clipIndex">Index of clip.</param>
223- public static void PlayGunSound ( this Player player , Vector3 position , FirearmType firearmType , float pitch = 1 , int clipIndex = 0 )
228+ [ Obsolete ( "This method is deprecated, use PlayGunSound(this Player, FirearmType, int, Vector3, MixerChannel, float, float) instead." ) ]
229+ public static void PlayGunSound ( this Player player , Vector3 position , FirearmType firearmType , float pitch = 1 , int clipIndex = 0 ) => player . PlayGunSound ( firearmType , clipIndex , position , pitch : pitch ) ;
230+
231+ /// <summary>
232+ /// Plays a gun sound that only the <paramref name="player"/> can hear.
233+ /// </summary>
234+ /// <param name="player">Target to play.</param>
235+ /// <param name="firearmType">Weapon's sound to play.</param>
236+ /// <param name="clipIndex">Index of clip.</param>
237+ /// <param name="position">Position to play on.</param>
238+ /// <param name="mixerChannel">Audio's mixer channel.</param>
239+ /// <param name="range">Max range of sound.</param>
240+ /// <param name="pitch">Speed of sound.</param>
241+ public static void PlayGunSound ( this Player player , FirearmType firearmType , int clipIndex , Vector3 position , MixerChannel mixerChannel = MixerChannel . Weapons , float ? range = null , float ? pitch = null )
224242 {
225- if ( firearmType is FirearmType . ParticleDisruptor or FirearmType . None )
243+ if ( firearmType is FirearmType . None )
244+ {
245+ Log . Error ( $ "Failed to play gun sound for player { player . Nickname } because firearm type was None.") ;
226246 return ;
247+ }
227248
228- Features . Items . Firearm firearm = Features . Items . Firearm . ItemTypeToFirearmInstance [ firearmType ] ;
249+ if ( ! InventoryItemLoader . TryGetItem ( firearmType . GetItemType ( ) , out ItemBase itemBase ) )
250+ {
251+ Log . Error ( $ "Failed to get ItemBase for firearm type { firearmType } when trying to play gun sound for player { player . Nickname } ") ;
252+ return ;
253+ }
229254
255+ Firearm firearm = Item . Get < Firearm > ( itemBase ) ;
230256 if ( firearm == null )
257+ {
258+ Log . Error ( $ "Failed to get Firearm for firearm type { firearmType } when trying to play gun sound.") ;
231259 return ;
260+ }
232261
233262 using ( NetworkWriterPooled writer = NetworkWriterPool . Get ( ) )
234263 {
235264 writer . WriteUShort ( NetworkMessageId < RoleSyncInfo > . Id ) ;
236- new RoleSyncInfo ( Server . Host . ReferenceHub , RoleTypeId . ClassD , player . ReferenceHub , null ) . Write ( writer ) ;
265+ new RoleSyncInfo ( Server . Host . ReferenceHub , RoleTypeId . Tutorial , player . ReferenceHub , null ) . Write ( writer ) ;
237266 writer . WriteRelativePosition ( new RelativePosition ( 0 , 0 , 0 , 0 , false ) ) ;
238267 writer . WriteUShort ( 0 ) ;
239268 player . Connection . Send ( writer ) ;
240269 }
241270
242- firearm . BarrelAmmo = 1 ;
243- firearm . BarrelMagazine . IsCocked = true ;
244271 player . SendFakeSyncVar ( Server . Host . Inventory . netIdentity , typeof ( Inventory ) , nameof ( Inventory . NetworkCurItem ) , firearm . Identifier ) ;
245272
246- if ( ! firearm . Base . TryGetModule ( out AudioModule audioModule ) )
247- return ;
248-
249- Timing . CallDelayed ( 0.1f , ( ) => // due to selecting item we need to delay shot a bit
273+ Timing . CallDelayed ( 0.1f , ( ) =>
250274 {
251- audioModule . SendRpc ( player . ReferenceHub , writer =>
252- audioModule . ServerSend ( writer , clipIndex , pitch , MixerChannel . Weapons , 12f , position , false ) ) ;
275+ if ( ! player . IsConnected )
276+ return ;
253277
278+ firearm . PlaySound ( player , clipIndex , mixerChannel , position , shooterVisible : false , range , pitch ) ;
254279 player . SendFakeSyncVar ( Server . Host . Inventory . netIdentity , typeof ( Inventory ) , nameof ( Inventory . NetworkCurItem ) , ItemIdentifier . None ) ;
255-
256280 player . Connection . Send ( new RoleSyncInfo ( Server . Host . ReferenceHub , Server . Host . Role , player . ReferenceHub , null ) ) ;
257281 } ) ;
258282 }
259283
284+ /// <summary>
285+ /// Plays a gun sound to the specified player.
286+ /// </summary>
287+ /// <param name="firearm">The firearm whose <see cref="AudioModule"/> to use.</param>
288+ /// <param name="target">The player to send the sound to.</param>
289+ /// <param name="index">The index of the audio clip to play.</param>
290+ /// <param name="channel">The <see cref="MixerChannel"/> to play the sound on.</param>
291+ /// <param name="position">The world position the sound originates from.</param>
292+ /// <param name="shooterVisible">Whether the shooter is visible to the target. If <see langword="false"/>, the sound will be played at <paramref name="position"/> instead of on the firearm's transform.</param>
293+ /// <param name="range">The range of the sound.</param>
294+ /// <param name="pitch">The pitch of the sound.</param>
295+ /// <returns><see langword="true"/> if the sound was played successfully; <see langword="false"/> if <see cref="AudioModule"/> is <see langword="null"/>.</returns>
296+ public static bool PlaySound ( this Firearm firearm , Player target , int index , MixerChannel channel , Vector3 position , bool shooterVisible , float ? range = null , float ? pitch = null )
297+ {
298+ AudioModule audioModule = firearm . AudioModule ;
299+ if ( audioModule == null )
300+ {
301+ Log . Error ( $ "Firearm { firearm } doesn't have an audio module.") ;
302+ return false ;
303+ }
304+
305+ if ( target == null )
306+ {
307+ Log . Error ( "Target player is null." ) ;
308+ return false ;
309+ }
310+
311+ if ( ! range . HasValue )
312+ range = audioModule . FinalGunshotRange ;
313+
314+ if ( ! pitch . HasValue )
315+ pitch = audioModule . RandomPitch ;
316+
317+ audioModule . SendRpc ( target . ReferenceHub , writer => audioModule . ServerSend ( writer , index , pitch . Value , channel , range . Value , position , shooterVisible ) ) ;
318+ return true ;
319+ }
320+
321+ /// <summary>
322+ /// Plays a gun sound to the specified players.
323+ /// </summary>
324+ /// <param name="firearm">The firearm whose <see cref="AudioModule"/> to use.</param>
325+ /// <param name="targets">The players to send the sound to.</param>
326+ /// <param name="index">The index of the audio clip to play.</param>
327+ /// <param name="channel">The <see cref="MixerChannel"/> to play the sound on.</param>
328+ /// <param name="position">The world position the sound originates from.</param>
329+ /// <param name="shooterVisible">Whether the shooter is visible to the target. If <see langword="false"/>, the sound will be played at <paramref name="position"/> instead of on the firearm's transform.</param>
330+ /// <param name="range">The range of the sound.</param>
331+ /// <param name="pitch">The pitch of the sound.</param>
332+ /// <returns><see langword="true"/> if the sound was played successfully; <see langword="false"/> if <see cref="AudioModule"/> is <see langword="null"/>.</returns>
333+ public static bool PlaySound ( this Firearm firearm , IEnumerable < Player > targets , int index , MixerChannel channel , Vector3 position , bool shooterVisible , float ? range = null , float ? pitch = null )
334+ {
335+ AudioModule audioModule = firearm . AudioModule ;
336+ if ( audioModule == null )
337+ {
338+ Log . Error ( $ "Firearm { firearm } doesn't have an audio module.") ;
339+ return false ;
340+ }
341+
342+ if ( targets == null )
343+ {
344+ Log . Error ( "Failed to play sound, targets is null." ) ;
345+ return false ;
346+ }
347+
348+ if ( ! range . HasValue )
349+ range = audioModule . FinalGunshotRange ;
350+
351+ if ( ! pitch . HasValue )
352+ pitch = audioModule . RandomPitch ;
353+
354+ HashSet < ReferenceHub > targetHubs = targets . Select ( p => p . ReferenceHub ) . ToHashSet ( ) ;
355+
356+ audioModule . SendRpc ( targetHubs . Contains , writer => audioModule . ServerSend ( writer , index , pitch . Value , channel , range . Value , position , shooterVisible ) ) ;
357+ return true ;
358+ }
359+
360+ /// <summary>
361+ /// Spawns a blood decal for this player.
362+ /// </summary>
363+ /// <param name="player">Target to spawn blood decal for.</param>
364+ /// <param name="position">The position of the blood decal.</param>
365+ /// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
366+ /// <returns><see langword="true"/> if the blood decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
367+ public static bool SpawnBlood ( this Player player , Vector3 position , Vector3 sourcePosition ) => SpawnDecal ( player , position , sourcePosition , DecalPoolType . Blood ) ;
368+
369+ /// <summary>
370+ /// Spawns a blood decal for the specified players.
371+ /// </summary>
372+ /// <param name="players">The players for which to spawn the blood decal.</param>
373+ /// <param name="position">The position of the blood decal.</param>
374+ /// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
375+ /// <returns><see langword="true"/> if the blood decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
376+ public static bool SpawnBlood ( this IEnumerable < Player > players , Vector3 position , Vector3 sourcePosition ) => SpawnDecal ( players , position , sourcePosition , DecalPoolType . Blood , FirearmType . Com15 ) ;
377+
378+ /// <summary>
379+ /// Spawns a decal for this player.
380+ /// </summary>
381+ /// <param name="player">Target to spawn decal for.</param>
382+ /// <param name="position">The position of the decal.</param>
383+ /// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
384+ /// <param name="decalType">The <see cref="Decals.DecalPoolType"/>.</param>
385+ /// <param name="firearmType">The <see cref="Enums.FirearmType"/> to use.</param>
386+ /// <returns><see langword="true"/> if the decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
387+ public static bool SpawnDecal ( this Player player , Vector3 position , Vector3 sourcePosition , DecalPoolType decalType , FirearmType firearmType = FirearmType . Com15 )
388+ {
389+ if ( ! InventoryItemLoader . TryGetItem ( firearmType . GetItemType ( ) , out ItemBase itemBase ) )
390+ {
391+ Log . Error ( $ "Failed to spawn decal: Could not find a Firearm for { firearmType } .") ;
392+ return false ;
393+ }
394+
395+ Firearm firearm = Item . Get < Firearm > ( itemBase ) ;
396+ if ( firearm == null )
397+ {
398+ Log . Error ( $ "Failed to spawn decal: Could not find a Firearm for { firearmType } .") ;
399+ return false ;
400+ }
401+
402+ ImpactEffectsModule impactEffectsModule = firearm . ImpactEffectsModule ;
403+ if ( impactEffectsModule == null )
404+ {
405+ Log . Error ( $ "Failed to spawn decal: Could not find an ImpactEffectsModule for { firearmType } .") ;
406+ return false ;
407+ }
408+
409+ impactEffectsModule . SendRpc ( player . ReferenceHub , writer =>
410+ {
411+ writer . WriteSubheader ( ImpactEffectsModule . RpcType . ImpactDecal ) ;
412+ writer . WriteByte ( ( byte ) decalType ) ;
413+ writer . WriteRelativePosition ( new RelativePosition ( position ) ) ;
414+ writer . WriteRelativePosition ( new RelativePosition ( sourcePosition ) ) ;
415+ } ) ;
416+
417+ return true ;
418+ }
419+
420+ /// <summary>
421+ /// Spawns a decal for the specified targets.
422+ /// </summary>
423+ /// <param name="targets">The targets for which to spawn the decal.</param>
424+ /// <param name="position">The position of the decal.</param>
425+ /// <param name="sourcePosition">The raycast origin used to determine the decal's orientation.</param>
426+ /// <param name="decalType">The <see cref="Decals.DecalPoolType"/>.</param>
427+ /// <param name="firearmType">The <see cref="Enums.FirearmType"/> to use.</param>
428+ /// <returns><see langword="true"/> if the decal was successfully spawned; otherwise, <see langword="false"/>.</returns>
429+ public static bool SpawnDecal ( this IEnumerable < Player > targets , Vector3 position , Vector3 sourcePosition , DecalPoolType decalType , FirearmType firearmType = FirearmType . Com15 )
430+ {
431+ if ( ! InventoryItemLoader . TryGetItem ( firearmType . GetItemType ( ) , out ItemBase itemBase ) )
432+ {
433+ Log . Error ( $ "Failed to spawn decal: Could not find a Firearm for { firearmType } .") ;
434+ return false ;
435+ }
436+
437+ Firearm firearm = Item . Get < Firearm > ( itemBase ) ;
438+ if ( firearm == null )
439+ {
440+ Log . Error ( $ "Failed to spawn decal: Could not find a Firearm for { firearmType } .") ;
441+ return false ;
442+ }
443+
444+ ImpactEffectsModule impactEffectsModule = firearm . ImpactEffectsModule ;
445+ if ( impactEffectsModule == null )
446+ {
447+ Log . Error ( $ "Failed to spawn decal: Could not find an ImpactEffectsModule for { firearmType } .") ;
448+ return false ;
449+ }
450+
451+ HashSet < ReferenceHub > targetHubs = targets . Select ( p => p . ReferenceHub ) . ToHashSet ( ) ;
452+
453+ impactEffectsModule . SendRpc ( targetHubs . Contains , writer =>
454+ {
455+ writer . WriteSubheader ( ImpactEffectsModule . RpcType . ImpactDecal ) ;
456+ writer . WriteByte ( ( byte ) decalType ) ;
457+ writer . WriteRelativePosition ( new RelativePosition ( position ) ) ;
458+ writer . WriteRelativePosition ( new RelativePosition ( sourcePosition ) ) ;
459+ } ) ;
460+
461+ return true ;
462+ }
463+
260464 /// <summary>
261465 /// Place blood that only the <paramref name="player"/> can see.
262466 /// </summary>
@@ -265,6 +469,8 @@ public static void PlayGunSound(this Player player, Vector3 position, FirearmTyp
265469 /// <param name="origin">The direction of the blood decal.</param>
266470 /// <param name="roleTypeId">The RoleTypeId from who blood come from.</param>
267471 /// <param name="gettingShotSoundIndex">The sound than player get when getting shot.</param>
472+ [ Obsolete ( "Use Player::SpawnBlood(Vector3, Vector3) instead." ) ]
473+ #pragma warning disable IDE0060 // TODO: Deleted the unused param
268474 public static void PlaceBlood ( this Player player , Vector3 position , Vector3 origin , RoleTypeId roleTypeId , int gettingShotSoundIndex )
269475 {
270476 if ( ! roleTypeId . TryGetRoleBase ( out PlayerRoleBase playerRoleBase ) || playerRoleBase is not IBleedableRole )
0 commit comments