Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ riderModule.iml
/_ReSharper.Caches/
.idea
/tools
.vscode
.vs*
*.DotSettings.user
1 change: 1 addition & 0 deletions Reactor.Example/CustomRpcs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public enum CustomRpcCalls : uint
{
Example,
MethodRpcExample,
TargetRpcExample,
}
6 changes: 6 additions & 0 deletions Reactor.Example/ExamplePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,10 @@ public static void RpcSay(PlayerControl player, string text, float number, Playe
{
Logger<ExamplePlugin>.Info($"{player.Data.PlayerName} text: {text} number: {number} testPlayer: {testPlayer.NetId}");
}

[MethodRpc((uint) CustomRpcCalls.TargetRpcExample, nameof(target))]
public static void RpcSayTo(PlayerControl player, PlayerControl target, string text)
{
Logger<ExamplePlugin>.Info($"{player.Data.PlayerName} text: {text} sentOnlyTo: {target.NetId}");
}
}
20 changes: 18 additions & 2 deletions Reactor/Networking/Attributes/MethodRpcAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public sealed class MethodRpcAttribute : Attribute
/// </summary>
public uint Id { get; }

/// <summary>
/// Gets the method's parameter to get the target client, if any.
/// </summary>
public string? TargetParameter { get; }

/// <summary>
/// Gets or sets the send option of the rpc.
/// </summary>
Expand All @@ -43,9 +48,20 @@ public sealed class MethodRpcAttribute : Attribute
/// Initializes a new instance of the <see cref="MethodRpcAttribute"/> class.
/// </summary>
/// <param name="id">The id of the rpc.</param>
public MethodRpcAttribute(uint id)
Comment thread
TheDavSmasher marked this conversation as resolved.
/// <param name="targetParam">The parameter to get the target client from, if any.</param>
public MethodRpcAttribute(uint id, string? targetParam)
{
Id = id;
TargetParameter = targetParam;
}

/// <summary>
/// Initializes a new instance of the <see cref="MethodRpcAttribute"/> class.
/// </summary>
/// <param name="id">The id of the rpc.</param>
public MethodRpcAttribute(uint id)
: this(id, null)
{
}

/// <summary>
Expand All @@ -72,7 +88,7 @@ public static void Register(Assembly assembly, BasePlugin plugin)

try
{
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.SendOption, attribute.LocalHandling);
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.SendOption, attribute.LocalHandling, attribute.TargetParameter);
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
}
catch (Exception e)
Expand Down
54 changes: 49 additions & 5 deletions Reactor/Networking/Rpc/MethodRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ public class MethodRpc : UnsafeCustomRpc
/// <param name="id">The id of the rpc.</param>
/// <param name="option">The send option of the rpc.</param>
/// <param name="localHandling">The local handling method of the rpc.</param>
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling) : base(plugin, id)
/// <param name="targetParam">The parameter to get the target client from, if any.</param>
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, string? targetParam) : base(plugin, id)
{
Method = method;
LocalHandling = localHandling;
SendOption = option;
TargetParameter = targetParam;

var parameters = method.GetParameters();

Expand Down Expand Up @@ -66,6 +68,20 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
InnerNetObjectType = method.DeclaringType;
}

if (TargetParameter != null)
{
var param = Array.Find(parameters, p => p.Name == TargetParameter);
if (param == null)
{
throw new ArgumentException("Target Client parameter isn't part of method rpc signature.", nameof(method));
}
if (!typeof(PlayerControl).IsAssignableFrom(param.ParameterType) ||
!typeof(int).IsAssignableFrom(param.ParameterType))
{
throw new ArgumentException("Target Client parameter has to be a PlayerControl or int", nameof(method));
}
}

_handle = Hook(method, parameters, method.IsStatic);
}

Expand All @@ -78,9 +94,10 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
/// <param name="option">The send option of the rpc.</param>
/// <param name="localHandling">The local handling method of the rpc.</param>
/// <param name="sendImmediately">The value indicating whether the rpc should be sent immediately.</param>
/// <param name="targetParam">The parameter to get the target client from, if any.</param>
[Obsolete("Non-immediate RPCs were removed in 2025.5.20. All RPCs are immediate. Remove sendImmediately from the parameter list.")]
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately)
: this(plugin, method, id, option, localHandling)
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately, string? targetParam)
: this(plugin, method, id, option, localHandling, targetParam)
{
SendImmediately = sendImmediately;
}
Expand All @@ -90,6 +107,11 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
/// </summary>
public MethodInfo Method { get; }

/// <summary>
/// Gets the method's parameter to get the target client, if any.
/// </summary>
public string? TargetParameter { get; }

/// <inheritdoc />
protected internal override bool IsSingleton => false;

Expand Down Expand Up @@ -147,9 +169,10 @@ public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
/// </summary>
/// <param name="innerNetObject">The <see cref="InnerNetObject"/> to send the rpc on.</param>
/// <param name="args">The arguments to serialize and send.</param>
public void Send(InnerNetObject innerNetObject, object[] args)
/// <param name="targetClientId">The id of the target client. -1 broadcasts without specific target.</param>
public void Send(InnerNetObject innerNetObject, object[] args, int targetClientId = -1)
{
UnsafeSend(innerNetObject, args);
UnsafeSend(innerNetObject, args, targetClientId: targetClientId);
}

private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send));
Expand All @@ -176,6 +199,12 @@ DynamicMethod GenerateSender()
dynamicMethod.DefineParameter(parameter.Position + (isStatic ? 0 : 1), ParameterAttributes.None, parameter.Name);
}

var targetClientIndex = -1;
if (TargetParameter != null)
{
targetClientIndex = Array.FindIndex(parameters, p => p.Name == TargetParameter);
}

var il = dynamicMethod.GetILGenerator();

// :yeefuckinhaw:
Expand Down Expand Up @@ -231,6 +260,21 @@ DynamicMethod GenerateSender()
il.Emit(OpCodes.Stelem_Ref);
}

if (targetClientIndex >= 0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: review this IL code, as I'm not familiar with it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poke on this

{
il.Emit(OpCodes.Ldarg, targetClientIndex);
Comment thread
TheDavSmasher marked this conversation as resolved.
Outdated

var parameter = parameters[targetClientIndex];
if (typeof(PlayerControl).IsAssignableFrom(parameter.ParameterType))
Comment thread
TheDavSmasher marked this conversation as resolved.
Outdated
{
il.Emit(OpCodes.Ldfld, typeof(PlayerControl).GetField(nameof(PlayerControl.PlayerId), BindingFlags.Instance | BindingFlags.Public)!);
Comment thread
TheDavSmasher marked this conversation as resolved.
Outdated
}
}
else
{
il.Emit(OpCodes.Ldc_I4_M1);
}

il.Emit(OpCodes.Call, _sendMethod);

if (method.ReturnType != typeof(void))
Expand Down
Loading