diff --git a/EXILED/Exiled.Events/EventArgs/Item/DisruptorFiringEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Item/DisruptorFiringEventArgs.cs new file mode 100644 index 0000000000..ad3feef017 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Item/DisruptorFiringEventArgs.cs @@ -0,0 +1,56 @@ +// ----------------DissolveMatPool------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Item +{ + using Exiled.API.Features.Pickups; + using Interfaces; + using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Pickups; + + /// + /// Contains all information before a pickup shoot while on the ground. + /// + public class DisruptorFiringEventArgs : IDeniableEvent, IPickupEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public DisruptorFiringEventArgs(Pickup disruptor, API.Features.Player attacker, DisruptorActionModule.FiringState state, bool isAllowed = true) + { + Pickup = disruptor; + Attacker = attacker; + State = state; + IsAllowed = isAllowed; + } + + /// + /// Gets or Sets a value indicating whether the disruptor shoot and the ground. + /// The client will still see all effects, like sounds and shoot. + /// + public bool IsAllowed { get; set; } + + /// + /// Gets or Sets whether is the attacker. + /// + public API.Features.Player Attacker { get; set; } + + /// + /// Gets the state of the weapon. + /// + public DisruptorActionModule.FiringState State { get; } + + /// + /// Gets the pickup who shot the bullet. + /// + public Pickup Pickup { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Handlers/Item.cs b/EXILED/Exiled.Events/Handlers/Item.cs index 1c3f8b15e0..d8153a586e 100644 --- a/EXILED/Exiled.Events/Handlers/Item.cs +++ b/EXILED/Exiled.Events/Handlers/Item.cs @@ -54,10 +54,23 @@ public static class Item public static Event UsingRadioPickupBattery { get; set; } = new(); /// - /// Invoked before a state is changed. + /// Invoked before a state is changed. /// public static Event ChangingMicroHIDPickupState { get; set; } = new(); + /// + /// Invoked before a firing while on the ground. + /// The client will still see all effects, like sounds and shoot. + /// + public static Event DisruptorFiring { get; set; } = new(); + + /// + /// Called before a firing while on the ground. + /// WARNING: Client still receive the shoot sound AND the ammo is still removed. (even if = false). + /// + /// The instance. + public static void OnDisruptorFiring(DisruptorFiringEventArgs ev) => DisruptorFiring.InvokeSafely(ev); + /// /// Called before the ammo of an firearm is changed. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Item/DisruptorFiring.cs b/EXILED/Exiled.Events/Patches/Events/Item/DisruptorFiring.cs new file mode 100644 index 0000000000..889cbada1c --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Item/DisruptorFiring.cs @@ -0,0 +1,89 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Item +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using Exiled.API.Features; + using Exiled.API.Features.Pickups; + using Exiled.API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Item; + using Footprinting; + using HarmonyLib; + using InventorySystem.Items; + using InventorySystem.Items.Firearms; + using InventorySystem.Items.Firearms.Extensions; + using InventorySystem.Items.Firearms.Modules; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . Adds the event. + /// + [EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.DisruptorFiring))] + [HarmonyPatch(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension.ServerFire))] + public class DisruptorFiring + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder ev = generator.DeclareLocal(typeof(DisruptorFiringEventArgs)); + Label continueLabel = generator.DefineLabel(); + newInstructions.InsertRange(0, new CodeInstruction[] + { + // Pickup.Get(this._worldmodel.Identifier.SerialNumber) + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension._worldmodel))), + new(OpCodes.Callvirt, PropertyGetter(typeof(FirearmWorldmodel), nameof(FirearmWorldmodel.Identifier))), + new(OpCodes.Ldfld, Field(typeof(ItemIdentifier), nameof(ItemIdentifier.SerialNumber))), + new(OpCodes.Call, Method(typeof(Pickup), nameof(Pickup.Get), new[] { typeof(ushort) })), + + // Player.Get(this._scheduledAttackerFootprint) + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension._scheduledAttackerFootprint))), + new(OpCodes.Call, Method(typeof(API.Features.Player), nameof(API.Features.Player.Get), new[] { typeof(Footprint) })), + + // this._scheduledFiringState + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension._scheduledFiringState))), + + // true + new(OpCodes.Ldc_I4_0), + + // DisruptorFiringEventArgs ev = new DisruptorFiringEventArgs(Pickup.Get(this._worldmodel.Identifier.SerialNumber), Player.Get(this._scheduledAttackerFootprint), this._scheduledFiringState, true) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(DisruptorFiringEventArgs))[0]), + new(OpCodes.Stloc_S, ev.LocalIndex), + + // Handlers.Item.OnDisruptorFiring(ev); + new(OpCodes.Ldloc_S, ev.LocalIndex), + new(OpCodes.Call, Method(typeof(Handlers.Item), nameof(Handlers.Item.OnDisruptorFiring))), + + // if (!ev.IsAllowed) return; + new(OpCodes.Ldloc_S, ev.LocalIndex), + new (OpCodes.Callvirt, PropertyGetter(typeof(DisruptorFiringEventArgs), nameof(DisruptorFiringEventArgs.IsAllowed))), + new(OpCodes.Brtrue_S, continueLabel), + new(OpCodes.Ret), + + // this._scheduledAttackerFootprint = Attacker.Footprint; + new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex).WithLabels(continueLabel), + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(DisruptorFiringEventArgs), nameof(DisruptorFiringEventArgs.Attacker))), + new(OpCodes.Callvirt, PropertyGetter(typeof(API.Features.Player), nameof(API.Features.Player.Footprint))), + new(OpCodes.Stfld, Field(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension._scheduledAttackerFootprint))), + }); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file