Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Item/DisruptorFiringEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// ----------------DissolveMatPool-------------------------------------------------------
// <copyright file="DisruptorFiringEventArgs.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.Events.EventArgs.Item
{
using Exiled.API.Features.Pickups;
using Interfaces;
using InventorySystem.Items.Firearms.Modules;
using InventorySystem.Items.Pickups;

/// <summary>
/// Contains all information before a pickup <see cref="ItemType.ParticleDisruptor"/> shoot while on the ground.
/// </summary>
public class DisruptorFiringEventArgs : IDeniableEvent, IPickupEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="DisruptorFiringEventArgs"/> class.
/// </summary>
/// <param name="disruptor"><inheritdoc cref="Pickup"/></param>
/// <param name="attacker"><inheritdoc cref="Attacker"/></param>
/// <param name="state"><inheritdoc cref="State"/></param>
/// <param name="isAllowed"><inheritdoc cref="IsAllowed"/></param>
public DisruptorFiringEventArgs(Pickup disruptor, API.Features.Player attacker, DisruptorActionModule.FiringState state, bool isAllowed = true)
{
Pickup = disruptor;
Attacker = attacker;
State = state;
IsAllowed = isAllowed;
}

/// <summary>
/// Gets or Sets a value indicating whether the disruptor shoot and the ground.
/// <remarks>The client will still see all effects, like sounds and shoot.</remarks>
/// </summary>
public bool IsAllowed { get; set; }

/// <summary>
/// Gets or Sets whether is the attacker.
/// </summary>
public API.Features.Player Attacker { get; set; }

/// <summary>
/// Gets the state of the weapon.
/// </summary>
public DisruptorActionModule.FiringState State { get; }

/// <summary>
/// Gets the pickup who shot the bullet.
/// </summary>
public Pickup Pickup { get; }
}
}
15 changes: 14 additions & 1 deletion EXILED/Exiled.Events/Handlers/Item.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,23 @@ public static class Item
public static Event<UsingRadioPickupBatteryEventArgs> UsingRadioPickupBattery { get; set; } = new();

/// <summary>
/// Invoked before a<see cref="API.Features.Pickups.MicroHIDPickup"/> state is changed.
/// Invoked before a <see cref="API.Features.Pickups.MicroHIDPickup"/> state is changed.
/// </summary>
public static Event<ChangingMicroHIDPickupStateEventArgs> ChangingMicroHIDPickupState { get; set; } = new();

/// <summary>
/// Invoked before a <see cref="ItemType.ParticleDisruptor"/> firing while on the ground.
/// <remarks>The client will still see all effects, like sounds and shoot.</remarks>
/// </summary>
public static Event<DisruptorFiringEventArgs> DisruptorFiring { get; set; } = new();

/// <summary>
/// Called before a <see cref="ItemType.ParticleDisruptor"/> firing while on the ground.
/// WARNING: Client still receive the shoot sound AND the ammo is still removed. (even if <see cref="DisruptorFiringEventArgs.IsAllowed"/> = false).
/// </summary>
/// <param name="ev">The <see cref="DisruptorFiringEventArgs"/> instance.</param>
public static void OnDisruptorFiring(DisruptorFiringEventArgs ev) => DisruptorFiring.InvokeSafely(ev);

/// <summary>
/// Called before the ammo of an firearm is changed.
/// </summary>
Expand Down
89 changes: 89 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Item/DisruptorFiring.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// -----------------------------------------------------------------------
// <copyright file="DisruptorFiring.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

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;

/// <summary>
/// Patches <see cref="DisruptorWorldmodelActionExtension.ServerFire"/>. Adds the <see cref="DisruptorFiring"/> event.
/// </summary>
[EventPatch(typeof(Handlers.Item), nameof(Handlers.Item.DisruptorFiring))]
[HarmonyPatch(typeof(DisruptorWorldmodelActionExtension), nameof(DisruptorWorldmodelActionExtension.ServerFire))]
public class DisruptorFiring
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.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<CodeInstruction>.Pool.Return(newInstructions);
}
}
}