diff --git a/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs
new file mode 100644
index 0000000000..1dc1934942
--- /dev/null
+++ b/EXILED/Exiled.Events/EventArgs/Scp096/RemovingTargetEventArgs.cs
@@ -0,0 +1,59 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.EventArgs.Scp096
+{
+ using API.Features;
+
+ using Interfaces;
+
+ using Scp096Role = API.Features.Roles.Scp096Role;
+
+ ///
+ /// Contains all information after removing a target from SCP-096.
+ ///
+ public class RemovingTargetEventArgs : IScp096Event, IDeniableEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public RemovingTargetEventArgs(Player scp096, Player target, bool isAllowed = true)
+ {
+ Player = scp096;
+ Scp096 = scp096.Role.As();
+ Target = target;
+ IsAllowed = isAllowed;
+ }
+
+ ///
+ /// Gets the that is controlling SCP-096.
+ ///
+ public Player Player { get; }
+
+ ///
+ public Scp096Role Scp096 { get; }
+
+ ///
+ /// Gets the being removed as a target.
+ ///
+ public Player Target { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the target is allowed to be removed.
+ ///
+ public bool IsAllowed { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Events/Handlers/Scp096.cs b/EXILED/Exiled.Events/Handlers/Scp096.cs
index 76a6052fb8..7ddf935fb5 100644
--- a/EXILED/Exiled.Events/Handlers/Scp096.cs
+++ b/EXILED/Exiled.Events/Handlers/Scp096.cs
@@ -32,6 +32,11 @@ public static class Scp096
///
public static Event AddingTarget { get; set; } = new();
+ ///
+ /// Invoked before removing a target from SCP-096.
+ ///
+ public static Event RemovingTarget { get; set; } = new();
+
///
/// Invoked before SCP-096 begins prying open a gate.
///
@@ -65,6 +70,12 @@ public static class Scp096
/// The instance.
public static void OnAddingTarget(AddingTargetEventArgs ev) => AddingTarget.InvokeSafely(ev);
+ ///
+ /// Called before removing a target from SCP-096.
+ ///
+ /// The instance.
+ public static void OnRemovingTarget(RemovingTargetEventArgs ev) => RemovingTarget.InvokeSafely(ev);
+
///
/// Called before SCP-096 begins prying open a gate.
///
diff --git a/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs b/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs
new file mode 100644
index 0000000000..869988bda6
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Events/Scp096/RemovingTarget.cs
@@ -0,0 +1,149 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Events.Scp096
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Reflection.Emit;
+
+ using API.Features;
+ using API.Features.Pools;
+ using Exiled.Events.Attributes;
+ using Exiled.Events.EventArgs.Scp096;
+ using HarmonyLib;
+ using PlayerRoles.PlayableScps.Scp096;
+ using PlayerRoles.Subroutines;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patches and .
+ /// Adds the event.
+ ///
+ [EventPatch(typeof(Handlers.Scp096), nameof(Handlers.Scp096.RemovingTarget))]
+ [HarmonyPatch(typeof(Scp096TargetsTracker))]
+ internal static class RemovingTarget
+ {
+ [HarmonyPatch(nameof(Scp096TargetsTracker.RemoveTarget))]
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ Label retFalseLabel = generator.DefineLabel();
+ Label runLabel = generator.DefineLabel();
+
+ // make game check contains instead of removing then forcibly running our event (so no spam event calls and is still deniable)
+ newInstructions.Find(instruction => instruction.Calls(Method(typeof(HashSet), nameof(HashSet.Remove)))).operand = Method(typeof(HashSet), nameof(HashSet.Contains));
+
+ int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) - 1;
+
+ newInstructions[index].WithLabels(retFalseLabel);
+
+ index -= 1;
+
+ // integrate our condition into first if statement
+ newInstructions.RemoveAt(index);
+
+ newInstructions.InsertRange(
+ index,
+ new CodeInstruction[]
+ {
+ // integrate our condition into first if statement
+ new(OpCodes.Brfalse, retFalseLabel),
+
+ // Player.Get(base.Owner)
+ new(OpCodes.Ldarg_0),
+ new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))),
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
+
+ // Player.Get(target)
+ new(OpCodes.Ldarg_1),
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
+
+ // true
+ new(OpCodes.Ldc_I4_1),
+
+ // RemovingTargetEventArgs ev = new(scp096, target, isAllowed)
+ new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]),
+ new(OpCodes.Dup),
+
+ // Handlers.Scp096.OnRemovingTarget(ev)
+ new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))),
+
+ // if (!ev.IsAllowed)
+ // return;
+ new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))),
+ new(OpCodes.Brtrue, runLabel),
+ });
+
+ index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + 1;
+
+ // if allowed, remove target
+ newInstructions.InsertRange(index, new[]
+ {
+ new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]).WithLabels(runLabel),
+ new(OpCodes.Ldfld, Field(typeof(Scp096TargetsTracker), nameof(Scp096TargetsTracker.Targets))),
+ new(OpCodes.Ldarg_1),
+ new(OpCodes.Callvirt, Method(typeof(HashSet), nameof(HashSet.Remove))),
+ new(OpCodes.Pop),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+
+ [HarmonyPatch(nameof(Scp096TargetsTracker.ClearAllTargets))]
+ [HarmonyTranspiler]
+ private static IEnumerable Transpiler2(IEnumerable instructions)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ object continueLabel = newInstructions.Find(instruction => instruction.opcode == OpCodes.Br_S).operand;
+
+ const int offset = 1;
+ int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Stloc_1) + offset;
+
+ newInstructions.InsertRange(
+ index,
+ new[]
+ {
+ // Player.Get(base.Owner)
+ new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
+ new(OpCodes.Call, PropertyGetter(typeof(StandardSubroutine), nameof(StandardSubroutine.Owner))),
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
+
+ // Player.Get(target)
+ new(OpCodes.Ldloc_1),
+ new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })),
+
+ // true
+ new(OpCodes.Ldc_I4_1),
+
+ // RemovingTargetEventArgs ev = new(scp096, target, isAllowed)
+ new(OpCodes.Newobj, GetDeclaredConstructors(typeof(RemovingTargetEventArgs))[0]),
+ new(OpCodes.Dup),
+
+ // Handlers.Scp096.OnRemovingTarget(ev)
+ new(OpCodes.Call, Method(typeof(Handlers.Scp096), nameof(Handlers.Scp096.OnRemovingTarget))),
+
+ // if (!ev.IsAllowed)
+ // continue;
+ new(OpCodes.Callvirt, PropertyGetter(typeof(RemovingTargetEventArgs), nameof(RemovingTargetEventArgs.IsAllowed))),
+ new(OpCodes.Brfalse, continueLabel),
+ });
+
+ for (int z = 0; z < newInstructions.Count; z++)
+ yield return newInstructions[z];
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file