diff --git a/Exiled.Events/EventArgs/Player/SendingCommandEventArgs.cs b/Exiled.Events/EventArgs/Player/SendingCommandEventArgs.cs
new file mode 100644
index 0000000000..cd9db5eb6c
--- /dev/null
+++ b/Exiled.Events/EventArgs/Player/SendingCommandEventArgs.cs
@@ -0,0 +1,59 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Exiled Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.EventArgs.Player
+{
+ using CommandSystem;
+
+ using Exiled.API.Features;
+ using Exiled.Events.EventArgs.Interfaces;
+
+ ///
+ /// Contains all information before a player sends a proper RA command.
+ ///
+ public class SendingCommandEventArgs : IPlayerEvent, IDeniableEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SendingCommandEventArgs(Player player, ICommand command, string query)
+ {
+ Command = command;
+ Player = player;
+ Query = query;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the command can be executed.
+ ///
+ public bool IsAllowed { get; set; } = true;
+
+ ///
+ /// Gets the command that is being executed.
+ ///
+ public ICommand Command { get; }
+
+ ///
+ /// Gets the query of the command.
+ ///
+ public string Query { get; }
+
+ ///
+ /// Gets the player who's sending the command.
+ ///
+ public Player Player { get; }
+ }
+}
\ No newline at end of file
diff --git a/Exiled.Events/Handlers/Player.cs b/Exiled.Events/Handlers/Player.cs
index 4a9f686b43..352255fcb5 100644
--- a/Exiled.Events/Handlers/Player.cs
+++ b/Exiled.Events/Handlers/Player.cs
@@ -553,6 +553,11 @@ public class Player
///
public static Event ChangingNickname { get; set; } = new();
+ ///
+ /// Invoked before a 's sends proper RA command.
+ ///
+ public static Event SendingCommand { get; set; } = new();
+
///
/// Invoked before displaying the hitmarker to the player.
///
@@ -1198,6 +1203,12 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
/// The instance.
public static void OnChangingNickname(ChangingNicknameEventArgs ev) => ChangingNickname.InvokeSafely(ev);
+ ///
+ /// Called before a 's sends propper RA command.
+ ///
+ /// The instance.
+ public static void OnSendingCommand(SendingCommandEventArgs ev) => SendingCommand.InvokeSafely(ev);
+
///
/// Called before displaying the hitmarker to the player.
///
diff --git a/Exiled.Events/Patches/Events/Player/SendingCommand.cs b/Exiled.Events/Patches/Events/Player/SendingCommand.cs
new file mode 100644
index 0000000000..4ad5ebdf37
--- /dev/null
+++ b/Exiled.Events/Patches/Events/Player/SendingCommand.cs
@@ -0,0 +1,85 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Exiled 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 API.Features;
+ using API.Features.Core.Generic.Pools;
+
+ using CommandSystem;
+
+ using Exiled.Events.Attributes;
+ using Exiled.Events.EventArgs.Player;
+
+ using HarmonyLib;
+
+ using RemoteAdmin;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patches .
+ /// Adds the event.
+ ///
+ [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.SendingCommand))]
+ [HarmonyPatch(typeof(CommandProcessor), nameof(CommandProcessor.ProcessQuery))]
+ internal static class SendingCommand
+ {
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ Label ret = generator.DefineLabel();
+
+ newInstructions[newInstructions.Count - 1].labels.Add(ret);
+ LocalBuilder ev = generator.DeclareLocal(typeof(SendingCommandEventArgs));
+ const int offset = 2;
+ int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(CommandHandler), nameof(CommandHandler.TryGetCommand)))) + offset;
+
+ newInstructions.InsertRange(
+ index,
+ new[]
+ {
+ // Sender
+ new CodeInstruction(OpCodes.Ldarg_1),
+
+ // Player.Get(CommandSender)
+ new CodeInstruction(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(CommandSender) })),
+
+ // command
+ new CodeInstruction(OpCodes.Ldloc_1),
+
+ // query
+ new CodeInstruction(OpCodes.Ldarg_0),
+
+ // SendingCommandEventArgs ev = new SendingCommandEventArgs(Player, ICommand, Query)
+ new CodeInstruction(OpCodes.Newobj, GetDeclaredConstructors(typeof(SendingCommandEventArgs))[0]),
+ new CodeInstruction(OpCodes.Dup),
+ new CodeInstruction(OpCodes.Stloc_S, ev.LocalIndex),
+
+ // Handlers.Player.OnSendingCommand(ev)
+ new CodeInstruction(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnSendingCommand))),
+
+ // isallowed == false
+ new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex),
+ new CodeInstruction(OpCodes.Callvirt, PropertyGetter(typeof(SendingCommandEventArgs), nameof(SendingCommandEventArgs.IsAllowed))),
+
+ // ret
+ new CodeInstruction(OpCodes.Brfalse_S, ret),
+ });
+
+ foreach (CodeInstruction instruction in newInstructions)
+ yield return instruction;
+
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file