Skip to content

Commit d102f5f

Browse files
louis1706MS-crew
andcommitted
feat: Decal spawn, Playgunsound & FakeFire Methods / Firearm Module Propertys (#842)
by MS Co-Authored-By: MS <100300664+MS-crew@users.noreply.github.com>
1 parent 5874a17 commit d102f5f

4 files changed

Lines changed: 391 additions & 23 deletions

File tree

EXILED/Exiled.API/Extensions/MirrorExtensions.cs

Lines changed: 219 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)