diff --git a/EXILED/Exiled.Events/EventArgs/Player/ReceivingGunSoundEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ReceivingGunSoundEventArgs.cs new file mode 100644 index 000000000..b12fe1197 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/ReceivingGunSoundEventArgs.cs @@ -0,0 +1,101 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using API.Features; + using API.Features.Items; + + using AudioPooling; + + using Exiled.Events.EventArgs.Interfaces; + + using UnityEngine; + + /// + /// Contains all information before a player receive gun sound. + /// + public class ReceivingGunSoundEventArgs : IPlayerEvent, IDeniableEvent, IFirearmEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The referencehub who will receive gun sound. + /// The internal firearm instance. + /// The index of the audio clip to be played. + /// The audio mixer channel. + /// The audible range of the sound. + /// The pitch of the sound. + /// The audio owner position. + /// The audio owner is visible for this player. + public ReceivingGunSoundEventArgs(ReferenceHub hub, InventorySystem.Items.Firearms.Firearm firearm, int audioIndex, MixerChannel mixerChannel, float range, float pitch, Vector3 ownPos, bool isSenderVisible) + { + Player = Player.Get(hub); + Firearm = Item.Get(firearm); + Sender = Firearm.Owner; + Range = range; + Pitch = pitch; + AudioIndex = audioIndex; + MixerChannel = mixerChannel; + SenderPosition = ownPos; + SenderVisible = isSenderVisible; + } + + /// + /// Gets the player who will receive gun sound. + /// + public Player Player { get; } + + /// + /// Gets the player who owns the Firearm. + /// + public Player Sender { get; } + + /// + public Item Item => Firearm; + + /// + /// Gets the firearm that was the source of the sound. + /// + public Firearm Firearm { get; } + + /// + /// Gets or sets the index of the audio clip to be played from the firearm's audio list. + /// + public int AudioIndex { get; set; } + + /// + /// Gets or sets the mixer channel through which the sound will be played. + /// + public MixerChannel MixerChannel { get; set; } + + /// + /// Gets or sets the max audible distance of the gun sound. + /// + public float Range { get; set; } + + /// + /// Gets or sets the pitch of the gun sound. + /// + public float Pitch { get; set; } + + /// + /// Gets or sets the virtual origin point used(Only works when SenderVisible is false). + /// + public Vector3 SenderPosition { get; set; } + + /// + /// Gets a value indicating whether Sender is visible for this player. + /// + public bool SenderVisible { get; } + + /// + /// Gets or sets a value indicating whether the gun sound should be sent. + /// + public bool IsAllowed { get; set; } = true; + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/SendingGunSoundEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/SendingGunSoundEventArgs.cs new file mode 100644 index 000000000..c1041453a --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/SendingGunSoundEventArgs.cs @@ -0,0 +1,86 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.Events.EventArgs.Player +{ + using API.Features; + using API.Features.Items; + + using AudioPooling; + + using Exiled.Events.EventArgs.Interfaces; + + using UnityEngine; + + /// + /// Contains all information before a gun sound is sent to players. + /// + public class SendingGunSoundEventArgs : IPlayerEvent, IDeniableEvent, IFirearmEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The internal firearm instance. + /// The index of the audio clip to be played. + /// The audio mixer channel. + /// The audible range of the sound. + /// The pitch of the sound. + /// The audio owner position. + public SendingGunSoundEventArgs(InventorySystem.Items.Firearms.Firearm firearm, int audioIndex, MixerChannel mixerChannel, float range, float pitch, Vector3 ownPos) + { + Firearm = Item.Get(firearm); + Player = Firearm.Owner; + Range = range; + Pitch = pitch; + AudioIndex = audioIndex; + MixerChannel = mixerChannel; + SendingPosition = ownPos; + } + + /// + /// Gets the player who owns the Firearm. + /// + public Player Player { get; } + + /// + public Item Item => Firearm; + + /// + /// Gets the firearm that was the source of the sound. + /// + public Firearm Firearm { get; } + + /// + /// Gets or sets the index of the audio clip to be played from the firearm's audio list. + /// + public int AudioIndex { get; set; } + + /// + /// Gets or sets the mixer channel through which the sound will be played. + /// + public MixerChannel MixerChannel { get; set; } + + /// + /// Gets or sets the max audible distance of the gun sound. + /// + public float Range { get; set; } + + /// + /// Gets or sets the pitch of the gun sound. + /// + public float Pitch { get; set; } + + /// + /// Gets or sets the virtual origin point used(Only will work when this player not visible for sending player). + /// + public Vector3 SendingPosition { get; set; } + + /// + /// Gets or sets a value indicating whether the gun sound should be sent. + /// + public bool IsAllowed { get; set; } = true; + } +} diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index 6965e3e1c..c03bcebeb 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -279,6 +279,16 @@ public class Player /// public static Event Shooting { get; set; } = new(); + /// + /// Invoked before a sends a gun sound to nearby players. + /// + public static Event SendingGunSound { get; set; } = new(); + + /// + /// Invoked before a receives a gun sound. + /// + public static Event ReceivingGunSound { get; set; } = new(); + /// /// Invoked before a enters the pocket dimension. /// @@ -900,6 +910,18 @@ public static void OnRoomChanged(RoomChangedEventArgs ev) /// The instance. public static void OnShooting(ShootingEventArgs ev) => Shooting.InvokeSafely(ev); + /// + /// Called before the server sends a gun sound to nearby players. + /// + /// The instance. + public static void OnSendingGunSound(SendingGunSoundEventArgs ev) => SendingGunSound.InvokeSafely(ev); + + /// + /// Called when a receives a gun sound. + /// + /// The instance. + public static void OnReceivingGunSound(ReceivingGunSoundEventArgs ev) => ReceivingGunSound.InvokeSafely(ev); + /// /// Called before a enters the pocket dimension. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ReceivingGunSound.cs b/EXILED/Exiled.Events/Patches/Events/Player/ReceivingGunSound.cs new file mode 100644 index 000000000..46881a8d6 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ReceivingGunSound.cs @@ -0,0 +1,186 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.Events.Patches.Events.Player +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + + using HarmonyLib; + + using InventorySystem.Items.Firearms; + using InventorySystem.Items.Firearms.Modules; + + using static HarmonyLib.AccessTools; + + /// + /// Patches AudioModule.ServerSendToNearbyPlayers to add event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ReceivingGunSound))] + [HarmonyPatch] + public class ReceivingGunSound + { + private static Type displayClassType; + private static Type displayClassTypeForeach; + + private static MethodBase TargetMethod() + { + Type innerType = Inner(typeof(AudioModule), "<>c__DisplayClass31_1"); + + return Method(innerType, "b__0"); + } + + private static bool Prepare() + { + displayClassType = Inner(typeof(AudioModule), "<>c__DisplayClass31_0"); + displayClassTypeForeach = Inner(typeof(AudioModule), "<>c__DisplayClass31_1"); + + if (displayClassType == null || displayClassTypeForeach == null) + { + Log.Error("`<>c__DisplayClass31` _1 or _0 cannot found on ReceivingGunSound class. Class changed skipping patch."); + return false; + } + + return true; + } + + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(ReceivingGunSoundEventArgs)); + + Label ret = generator.DefineLabel(); + + int offset = 1; + int index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Stloc_0) + offset; + + newInstructions.InsertRange( + index, + [ + + // this.receiver + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "receiver")), + + // this.locals1.Firearm + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "<>4__this")), + new(OpCodes.Call, PropertyGetter(typeof(FirearmSubcomponentBase), nameof(FirearmSubcomponentBase.Firearm))), + + // this.locals1.index + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "index")), + + // this.locals1.channel + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "channel")), + + // this.locals1.audioRange + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "audioRange")), + + // this.locals1.pitch + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "pitch")), + + // this.locals1.ownPos + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(displayClassTypeForeach, "CS$<>8__locals1")), + new(OpCodes.Ldfld, Field(displayClassType, "ownPos")), + + // visibility flag + new(OpCodes.Ldloc_0), + + // new(receiver, firearm, audioIndex, mixerChannel, range, pitch, ownPos, isSenderVisible) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ReceivingGunSoundEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc, ev.LocalIndex), + + // Handlers.Player.OnReceivingGunSound(ev); + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnReceivingGunSound))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.IsAllowed))), + new CodeInstruction(OpCodes.Brfalse_S, ret), + ]); + + offset = -2; + const int count = 3; + + // index = ev.AudioIndex; + index = newInstructions.FindLastIndex(x => x.LoadsField(Field(displayClassType, "index"))) + offset; + newInstructions.RemoveRange(index, count); + newInstructions.InsertRange( + index, + [ + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.AudioIndex))), + ]); + + // pitch = ev.Pitch; + index = newInstructions.FindLastIndex(x => x.LoadsField(Field(displayClassType, "pitch"))) + offset; + newInstructions.RemoveRange(index, count); + newInstructions.InsertRange( + index, + [ + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.Pitch))), + ]); + + // channel = ev.MixerChannel; + index = newInstructions.FindLastIndex(x => x.LoadsField(Field(displayClassType, "channel"))) + offset; + newInstructions.RemoveRange(index, count); + newInstructions.InsertRange( + index, + [ + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.MixerChannel))), + ]); + + // audioRange = ev.Range; + index = newInstructions.FindLastIndex(x => x.LoadsField(Field(displayClassType, "audioRange"))) + offset; + newInstructions.RemoveRange(index, count); + newInstructions.InsertRange( + index, + [ + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.Range))), + ]); + + // ownPos = ev.SenderPosition; + index = newInstructions.FindLastIndex(x => x.LoadsField(Field(displayClassType, "ownPos"))) + offset; + newInstructions.RemoveRange(index, count); + newInstructions.InsertRange( + index, + [ + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(ReceivingGunSoundEventArgs), nameof(ReceivingGunSoundEventArgs.SenderPosition))), + ]); + + newInstructions[newInstructions.Count - 1].WithLabels(ret); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Player/SendingGunSound.cs b/EXILED/Exiled.Events/Patches/Events/Player/SendingGunSound.cs new file mode 100644 index 000000000..203a31dc5 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/SendingGunSound.cs @@ -0,0 +1,141 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.Events.Patches.Events.Player +{ + using System; + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + + using HarmonyLib; + + using InventorySystem.Items.Firearms; + using InventorySystem.Items.Firearms.Modules; + + using static HarmonyLib.AccessTools; + + /// + /// Patches AudioModule.ServerSendToNearbyPlayers to add event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.SendingGunSound))] + [HarmonyPatch(typeof(AudioModule), nameof(AudioModule.ServerSendToNearbyPlayers))] + public class SendingGunSound + { + private static Type displayClassType; + + private static bool Prepare() + { + displayClassType = Inner(typeof(AudioModule), "<>c__DisplayClass31_0"); + + if (displayClassType == null) + { + Log.Error("`<>c__DisplayClass31_0` cannot found on SendingGunSound class. Class changed skipping patch."); + return false; + } + + return true; + } + + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(SendingGunSoundEventArgs)); + + Label ret = generator.DefineLabel(); + + int index = newInstructions.FindLastIndex(x => x.Calls(PropertyGetter(typeof(ReferenceHub), nameof(ReferenceHub.AllHubs)))); + + newInstructions.InsertRange( + index, + [ + + // this.Firearm + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(FirearmSubcomponentBase), nameof(FirearmSubcomponentBase.Firearm))), + + // index + new(OpCodes.Ldarg_1), + + // channel + new(OpCodes.Ldarg_2), + + // audioRange + new(OpCodes.Ldarg_3), + + // pitch + new(OpCodes.Ldarg_S, 4), + + // ownPos + new(OpCodes.Ldloc_0), + new(OpCodes.Ldfld, Field(displayClassType, "ownPos")), + + // new(firearm, audioIndex, mixerChannel, range, pitch, ownPos) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(SendingGunSoundEventArgs))[0]), + new(OpCodes.Dup), + new(OpCodes.Dup), + new(OpCodes.Stloc, ev.LocalIndex), + + // Handlers.Player.OnSendingGunShotSound(ev); + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnSendingGunSound))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.IsAllowed))), + new(OpCodes.Brfalse_S, ret), + + // index = ev.AudioIndex; + new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.AudioIndex))), + new(OpCodes.Stfld, Field(displayClassType, "index")), + + // channel = ev.MixerChannel; + new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.MixerChannel))), + new(OpCodes.Stfld, Field(displayClassType, "channel")), + + // audioRange = ev.Range; + new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.Range))), + new(OpCodes.Stfld, Field(displayClassType, "audioRange")), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.Range))), + new(OpCodes.Ldc_R4, AudioModule.SendDistanceBuffer), + new(OpCodes.Add), + new(OpCodes.Dup), + new(OpCodes.Mul), + new(OpCodes.Stloc_2), + + // pitch = ev.Pitch; + new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.Pitch))), + new(OpCodes.Stfld, Field(displayClassType, "pitch")), + + // ownPos = ev.SendingPosition; + new(OpCodes.Ldloc_0), + new(OpCodes.Ldloc, ev.LocalIndex), + new(OpCodes.Callvirt, PropertyGetter(typeof(SendingGunSoundEventArgs), nameof(SendingGunSoundEventArgs.SendingPosition))), + new CodeInstruction(OpCodes.Stfld, Field(displayClassType, "ownPos")), + ]); + + newInstructions[newInstructions.Count - 1].WithLabels(ret); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +}