Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added SendingCommand event to Player #163

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// -----------------------------------------------------------------------
// <copyright file="SendingValidRACommandEventArgs.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.Player
{
using CommandSystem;
using Exiled.API.Features;
using Exiled.API.Features.Pickups;
using Exiled.Events.EventArgs.Interfaces;

using RemoteAdmin;

/// <summary>
/// Contains all information before a player sends a command.
/// </summary>
public class SendingValidRACommandEventArgs : IPlayerEvent, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="SendingValidRACommandEventArgs" /> class.
/// </summary>
/// <param name="player">
/// <inheritdoc cref="Player" />
/// </param>
/// <param name="command">
/// <inheritdoc cref="Command" />
/// </param>
/// <param name="query">
/// <inheritdoc cref="Query" />
/// </param>
/// <param name="response">
/// <inheritdoc cref="Response" />
/// </param>
public SendingValidRACommandEventArgs(Player player, ICommand command, string query, string response)
{
Player = player;
Command = command;
Query = query;
Response = response;
}

/// <summary>
/// Gets or sets a value indicating whether the player can send a command.
/// </summary>
public bool IsAllowed { get; set; } = true;

/// <summary>
/// Gets or sets the response of the command. If this value is null, the response will stay unchanged.
/// </summary>
public string Response { get; set; }

/// <summary>
/// Gets the player who is sending the command.
/// </summary>
public Player Player { get; }

/// <summary>
/// Gets the command query.
/// </summary>
public string Query { get; }

/// <summary>
/// Gets the command interface.
/// </summary>
public ICommand Command { get; }
}
}
11 changes: 11 additions & 0 deletions EXILED/Exiled.Events/Handlers/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,11 @@ public class Player
/// </summary>
public static Event<ChangingNicknameEventArgs> ChangingNickname { get; set; } = new();

/// <summary>
/// Invoked before a <see cref="API.Features.Player"/> sends valid RA command.
/// </summary>
public static Event<SendingValidRACommandEventArgs> SendingValidRACommand { get; set; } = new();

/// <summary>
/// Invoked before a player's emotion changed.
/// </summary>
Expand Down Expand Up @@ -1198,6 +1203,12 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
/// <param name="ev">The <see cref="ChangingNicknameEventArgs"/> instance.</param>
public static void OnChangingNickname(ChangingNicknameEventArgs ev) => ChangingNickname.InvokeSafely(ev);

/// <summary>
/// Called before a <see cref="Player"/> sends valid RA command.
/// </summary>
/// <param name="ev">The <see cref="SendingValidRACommandEventArgs"/> instance.</param>
public static void OnSendingValidRACommand(SendingValidRACommandEventArgs ev) => SendingValidRACommand.InvokeSafely(ev);

/// <summary>
/// Called before a <see cref="API.Features.Player"/>'s rotates the revolver.
/// </summary>
Expand Down
131 changes: 131 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Player/SendingValidRACommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// -----------------------------------------------------------------------
// <copyright file="SendingValidRACommand.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.Player
{
using System;
using System.Collections.Generic;
using System.Reflection.Emit;

using API.Features;
using API.Features.Pools;
using CommandSystem;
using Exiled.Events.Attributes;
using Exiled.Events.EventArgs.Player;

using HarmonyLib;

using RemoteAdmin;

using static HarmonyLib.AccessTools;

/// <summary>
/// Patches <see cref="CommandProcessor.ProcessQuery(string, CommandSender)" />.
/// Adds the <see cref="Handlers.Player.SendingValidRACommand" /> event.
/// </summary>
[EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.SendingValidRACommand))]
[HarmonyPatch(typeof(CommandProcessor), nameof(CommandProcessor.ProcessQuery))]
internal static class SendingValidRACommand
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

Label setptroperresp = generator.DefineLabel();

Label ret = generator.DefineLabel();
newInstructions[newInstructions.Count - 1].WithLabels(ret);
LocalBuilder ev = generator.DeclareLocal(typeof(SendingValidRACommandEventArgs));
int offset = 2;
int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(CommandHandler), nameof(CommandHandler.TryGetCommand)))) + offset;

Label contlabel = generator.DefineLabel();
newInstructions[index].WithLabels(contlabel);

int sendreplyidx = newInstructions.FindIndex(instructions => instructions.Calls(Method(typeof(string), nameof(string.IsNullOrEmpty)))) + offset;

Label sendreply = generator.DefineLabel();
newInstructions[sendreplyidx].WithLabels(sendreply);

newInstructions.InsertRange(
index,
new CodeInstruction[]
{
// sender
new (OpCodes.Ldarg_1),

// Player.get(sender)
new (OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new Type[] { typeof(CommandSender) })),

// command
new (OpCodes.Ldloc_1),

// query
new (OpCodes.Ldarg_0),

// response
new (OpCodes.Ldloc_S, 6),

// new SendingCommandEventArgs
new (OpCodes.Newobj, GetDeclaredConstructors(typeof(SendingValidRACommandEventArgs))[0]),
new (OpCodes.Dup),
new (OpCodes.Stloc_S, ev.LocalIndex),

// OnSendingCommad(ev)
new (OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnSendingValidRACommand))),

// if ev.IsAllowed cont
new (OpCodes.Ldloc_S, ev.LocalIndex),
new (OpCodes.Callvirt, PropertyGetter(typeof(SendingValidRACommandEventArgs), nameof(SendingValidRACommandEventArgs.IsAllowed))),
new (OpCodes.Brtrue_S, contlabel),

// if ev.Response.IsNullOrEmpty rets
new (OpCodes.Ldloc_S, ev.LocalIndex),
new (OpCodes.Callvirt, PropertyGetter(typeof (SendingValidRACommandEventArgs), nameof(SendingValidRACommandEventArgs.Response))),
new (OpCodes.Call, Method(typeof(string), nameof(string.IsNullOrEmpty))),
new (OpCodes.Brtrue_S, setptroperresp),

// response = ev.Response
new (OpCodes.Ldloc_S, ev.LocalIndex),
new (OpCodes.Callvirt, PropertyGetter(typeof (SendingValidRACommandEventArgs), nameof(SendingValidRACommandEventArgs.Response))),
new (OpCodes.Stloc_S, 6),

// goto sendreply
new (OpCodes.Br, sendreply),

// response = "The Command Execution Was Prevented By Plugin."
new CodeInstruction(OpCodes.Ldstr, "The Command Execution Was Prevented By Plugin.").WithLabels(setptroperresp),
new (OpCodes.Stloc_S, 6),
new (OpCodes.Br, sendreply),
});
offset = -4;
index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(string), nameof(string.ToUpperInvariant)))) + offset;
Label skip = generator.DefineLabel();
newInstructions[index].WithLabels(skip);
newInstructions.InsertRange(
index,
new CodeInstruction[]
{
// if ev.Response.IsNullOrEmpty skip
new (OpCodes.Ldloc_S, ev.LocalIndex),
new (OpCodes.Callvirt, PropertyGetter(typeof (SendingValidRACommandEventArgs), nameof(SendingValidRACommandEventArgs.Response))),
new (OpCodes.Call, Method(typeof(string), nameof(string.IsNullOrEmpty))),
new (OpCodes.Brtrue_S, skip),

// response = ev.Response
new (OpCodes.Ldloc_S, ev.LocalIndex),
new (OpCodes.Callvirt, PropertyGetter(typeof (SendingValidRACommandEventArgs), nameof(SendingValidRACommandEventArgs.Response))),
new (OpCodes.Stloc_S, 6),
});

for (int z = 0; z < newInstructions.Count; z++)
yield return newInstructions[z];

ListPool<CodeInstruction>.Pool.Return(newInstructions);
}
}
}
Loading