From c34bc8c760a246a76bae9a8825dc74406798d3e9 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 9 Jun 2026 18:20:00 -0600
Subject: [PATCH 01/17] Updated .gitignore
---
.gitignore | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index f04d6913..dd9fb015 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,5 @@ riderModule.iml
/_ReSharper.Caches/
.idea
/tools
-.vscode
+.vs*
*.DotSettings.user
\ No newline at end of file
From 5bf33225231a24d04cfcbbd09764afe5441b594b Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 9 Jun 2026 19:09:40 -0600
Subject: [PATCH 02/17] Added way to define a paramter as a target client
---
.../Networking/Attributes/MethodRpcAttribute.cs | 11 +++++++++--
Reactor/Networking/Rpc/MethodRpc.cs | 14 +++++++++++---
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/Reactor/Networking/Attributes/MethodRpcAttribute.cs b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
index b0fa1b09..f7e84739 100644
--- a/Reactor/Networking/Attributes/MethodRpcAttribute.cs
+++ b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
@@ -23,6 +23,11 @@ public sealed class MethodRpcAttribute : Attribute
///
public uint Id { get; }
+ ///
+ /// Gets the method's parameter to get the target client, if any.
+ ///
+ public string? TargetParameter { get; }
+
///
/// Gets or sets the send option of the rpc.
///
@@ -43,9 +48,11 @@ public sealed class MethodRpcAttribute : Attribute
/// Initializes a new instance of the class.
///
/// The id of the rpc.
- public MethodRpcAttribute(uint id)
+ /// The parameter to get the target client from, if any.
+ public MethodRpcAttribute(uint id, string? targetParam = null)
{
Id = id;
+ TargetParameter = targetParam;
}
///
@@ -72,7 +79,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.Instance.CustomRpcManager.Register(customRpc);
}
catch (Exception e)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index 49016355..c55c0b0f 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -32,11 +32,13 @@ public class MethodRpc : UnsafeCustomRpc
/// The id of the rpc.
/// The send option of the rpc.
/// The local handling method of the rpc.
- public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling) : base(plugin, id)
+ /// The parameter to get the target client from, if any.
+ 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();
@@ -78,9 +80,10 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
/// The send option of the rpc.
/// The local handling method of the rpc.
/// The value indicating whether the rpc should be sent immediately.
+ /// The parameter to get the target client from, if any.
[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;
}
@@ -90,6 +93,11 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
///
public MethodInfo Method { get; }
+ ///
+ /// Gets the method's parameter to get the target client, if any.
+ ///
+ public string? TargetParameter { get; }
+
///
protected internal override bool IsSingleton => false;
From 8656da0d8c4181da9da9556666efc4a403b24bc9 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 9 Jun 2026 19:59:10 -0600
Subject: [PATCH 03/17] Injected necessary IL to pass additional parameter
---
Reactor/Networking/Rpc/MethodRpc.cs | 40 +++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index c55c0b0f..4d0362ca 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -68,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);
}
@@ -155,9 +169,10 @@ public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
///
/// The to send the rpc on.
/// The arguments to serialize and send.
- public void Send(InnerNetObject innerNetObject, object[] args)
+ /// The id of the target client. -1 broadcasts without specific target.
+ 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));
@@ -184,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:
@@ -239,6 +260,21 @@ DynamicMethod GenerateSender()
il.Emit(OpCodes.Stelem_Ref);
}
+ if (targetClientIndex >= 0)
+ {
+ il.Emit(OpCodes.Ldarg, targetClientIndex);
+
+ var parameter = parameters[targetClientIndex];
+ if (typeof(PlayerControl).IsAssignableFrom(parameter.ParameterType))
+ {
+ il.Emit(OpCodes.Ldfld, typeof(PlayerControl).GetField(nameof(PlayerControl.PlayerId), BindingFlags.Instance | BindingFlags.Public)!);
+ }
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldc_I4_M1);
+ }
+
il.Emit(OpCodes.Call, _sendMethod);
if (method.ReturnType != typeof(void))
From ff5f44ccae6de6c0ffdbb949e9a6d3ec108020f7 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 9 Jun 2026 20:06:42 -0600
Subject: [PATCH 04/17] Fixed wrong constructor structure for annotation
---
Reactor/Networking/Attributes/MethodRpcAttribute.cs | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/Reactor/Networking/Attributes/MethodRpcAttribute.cs b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
index f7e84739..2ef06ba2 100644
--- a/Reactor/Networking/Attributes/MethodRpcAttribute.cs
+++ b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
@@ -49,12 +49,21 @@ public sealed class MethodRpcAttribute : Attribute
///
/// The id of the rpc.
/// The parameter to get the target client from, if any.
- public MethodRpcAttribute(uint id, string? targetParam = null)
+ public MethodRpcAttribute(uint id, string? targetParam)
{
Id = id;
TargetParameter = targetParam;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The id of the rpc.
+ public MethodRpcAttribute(uint id)
+ : this(id, null)
+ {
+ }
+
///
/// Registers all method rpc's annotated with in the specified .
///
From 206261fd97ee5064a066d85a5d4b55d459486642 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 8 Sep 2026 11:28:22 -0600
Subject: [PATCH 05/17] Added example Rpc with target
---
Reactor.Example/CustomRpcs.cs | 1 +
Reactor.Example/ExamplePlugin.cs | 6 ++++++
2 files changed, 7 insertions(+)
diff --git a/Reactor.Example/CustomRpcs.cs b/Reactor.Example/CustomRpcs.cs
index 8adea47a..637bd6fa 100644
--- a/Reactor.Example/CustomRpcs.cs
+++ b/Reactor.Example/CustomRpcs.cs
@@ -4,4 +4,5 @@ public enum CustomRpcCalls : uint
{
Example,
MethodRpcExample,
+ TargetRpcExample,
}
diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs
index c14e8b9c..3331340c 100644
--- a/Reactor.Example/ExamplePlugin.cs
+++ b/Reactor.Example/ExamplePlugin.cs
@@ -99,4 +99,10 @@ public static void RpcSay(PlayerControl player, string text, float number, Playe
{
Logger.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.Info($"{player.Data.PlayerName} text: {text} sentOnlyTo: {target.NetId}");
+ }
}
From 17ed3f92fac17b0c18d117a345363a24f0c0d380 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 09:51:09 -0600
Subject: [PATCH 06/17] Added isStatic offset to emitted ldarg
---
Reactor/Networking/Rpc/MethodRpc.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index 4d0362ca..bc0b331b 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -262,7 +262,7 @@ DynamicMethod GenerateSender()
if (targetClientIndex >= 0)
{
- il.Emit(OpCodes.Ldarg, targetClientIndex);
+ il.Emit(OpCodes.Ldarg, targetClientIndex + (isStatic ? 0 : 1));
var parameter = parameters[targetClientIndex];
if (typeof(PlayerControl).IsAssignableFrom(parameter.ParameterType))
From 2465d252d91fd7bce43541acf53d3dec148b2ffd Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 11:56:19 -0600
Subject: [PATCH 07/17] Changed to use InnerNetObject.OwnerId
---
Reactor/Networking/Rpc/MethodRpc.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index bc0b331b..f54a6de4 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -75,10 +75,10 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
{
throw new ArgumentException("Target Client parameter isn't part of method rpc signature.", nameof(method));
}
- if (!typeof(PlayerControl).IsAssignableFrom(param.ParameterType) ||
+ if (!typeof(InnerNetObject).IsAssignableFrom(param.ParameterType) ||
!typeof(int).IsAssignableFrom(param.ParameterType))
{
- throw new ArgumentException("Target Client parameter has to be a PlayerControl or int", nameof(method));
+ throw new ArgumentException("Target Client parameter has to be an InnerNetObject or int", nameof(method));
}
}
@@ -265,9 +265,9 @@ DynamicMethod GenerateSender()
il.Emit(OpCodes.Ldarg, targetClientIndex + (isStatic ? 0 : 1));
var parameter = parameters[targetClientIndex];
- if (typeof(PlayerControl).IsAssignableFrom(parameter.ParameterType))
+ if (typeof(InnerNetObject).IsAssignableFrom(parameter.ParameterType))
{
- il.Emit(OpCodes.Ldfld, typeof(PlayerControl).GetField(nameof(PlayerControl.PlayerId), BindingFlags.Instance | BindingFlags.Public)!);
+ il.Emit(OpCodes.Ldfld, typeof(InnerNetObject).GetField(nameof(InnerNetObject.OwnerId), BindingFlags.Instance | BindingFlags.Public)!);
}
}
else
From bb974f13fe930c12756cd5bfcc68d5ce118c8f19 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 13:41:35 -0600
Subject: [PATCH 08/17] Changed to use property getter instead of field
---
Reactor/Networking/Rpc/MethodRpc.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index f54a6de4..0410c5db 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -267,7 +267,7 @@ DynamicMethod GenerateSender()
var parameter = parameters[targetClientIndex];
if (typeof(InnerNetObject).IsAssignableFrom(parameter.ParameterType))
{
- il.Emit(OpCodes.Ldfld, typeof(InnerNetObject).GetField(nameof(InnerNetObject.OwnerId), BindingFlags.Instance | BindingFlags.Public)!);
+ il.Emit(OpCodes.Callvirt, typeof(InnerNetObject).GetProperty(nameof(InnerNetObject.OwnerId), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!);
}
}
else
From b06265893617ed27bd22f41a048cd64a81c06320 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 15:05:24 -0600
Subject: [PATCH 09/17] Removed redundant constructor with default parameter
---
Reactor/Networking/Attributes/MethodRpcAttribute.cs | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/Reactor/Networking/Attributes/MethodRpcAttribute.cs b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
index 2ef06ba2..f7e84739 100644
--- a/Reactor/Networking/Attributes/MethodRpcAttribute.cs
+++ b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
@@ -49,21 +49,12 @@ public sealed class MethodRpcAttribute : Attribute
///
/// The id of the rpc.
/// The parameter to get the target client from, if any.
- public MethodRpcAttribute(uint id, string? targetParam)
+ public MethodRpcAttribute(uint id, string? targetParam = null)
{
Id = id;
TargetParameter = targetParam;
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The id of the rpc.
- public MethodRpcAttribute(uint id)
- : this(id, null)
- {
- }
-
///
/// Registers all method rpc's annotated with in the specified .
///
From 9b6387283f69117435ebcb32d3e3691f546d21b1 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 15:09:25 -0600
Subject: [PATCH 10/17] Added default parameters
---
Reactor/Networking/Rpc/MethodRpc.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index 0410c5db..bd6e810c 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -33,7 +33,7 @@ public class MethodRpc : UnsafeCustomRpc
/// The send option of the rpc.
/// The local handling method of the rpc.
/// The parameter to get the target client from, if any.
- public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, string? targetParam) : base(plugin, id)
+ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, string? targetParam = null) : base(plugin, id)
{
Method = method;
LocalHandling = localHandling;
@@ -96,7 +96,7 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
/// The value indicating whether the rpc should be sent immediately.
/// The parameter to get the target client from, if any.
[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, string? targetParam)
+ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately, string? targetParam = null)
: this(plugin, method, id, option, localHandling, targetParam)
{
SendImmediately = sendImmediately;
From 7482d986704c4bd154e05c157c0e6af643a067a2 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 20:24:02 -0600
Subject: [PATCH 11/17] Added HostOnly option for MethodRpc
---
.../Attributes/MethodRpcAttribute.cs | 20 ++++++++++++++-
Reactor/Networking/Rpc/MethodRpc.cs | 25 ++++++++++++++-----
2 files changed, 38 insertions(+), 7 deletions(-)
diff --git a/Reactor/Networking/Attributes/MethodRpcAttribute.cs b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
index f7e84739..8a11f81c 100644
--- a/Reactor/Networking/Attributes/MethodRpcAttribute.cs
+++ b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
@@ -28,6 +28,11 @@ public sealed class MethodRpcAttribute : Attribute
///
public string? TargetParameter { get; }
+ ///
+ /// Gets a value indicating whether the RPC should only be sent to the host.
+ ///
+ public bool HostOnly { get; }
+
///
/// Gets or sets the send option of the rpc.
///
@@ -53,6 +58,19 @@ public MethodRpcAttribute(uint id, string? targetParam = null)
{
Id = id;
TargetParameter = targetParam;
+ HostOnly = false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The id of the rpc.
+ /// Whether to send the RPC only to the host.
+ public MethodRpcAttribute(uint id, bool hostOnly)
+ {
+ Id = id;
+ TargetParameter = null;
+ HostOnly = hostOnly;
}
///
@@ -79,7 +97,7 @@ public static void Register(Assembly assembly, BasePlugin plugin)
try
{
- var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.SendOption, attribute.LocalHandling, attribute.TargetParameter);
+ var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.SendOption, attribute.LocalHandling, attribute.TargetParameter, attribute.HostOnly);
PluginSingleton.Instance.CustomRpcManager.Register(customRpc);
}
catch (Exception e)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index bd6e810c..60efb5e5 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -33,12 +33,14 @@ public class MethodRpc : UnsafeCustomRpc
/// The send option of the rpc.
/// The local handling method of the rpc.
/// The parameter to get the target client from, if any.
- public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, string? targetParam = null) : base(plugin, id)
+ /// Whether to send the RPC only to the host.
+ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, string? targetParam = null, bool hostOnly = false) : base(plugin, id)
{
Method = method;
LocalHandling = localHandling;
SendOption = option;
TargetParameter = targetParam;
+ HostOnly = hostOnly;
var parameters = method.GetParameters();
@@ -68,7 +70,7 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
InnerNetObjectType = method.DeclaringType;
}
- if (TargetParameter != null)
+ if (!HostOnly && TargetParameter != null)
{
var param = Array.Find(parameters, p => p.Name == TargetParameter);
if (param == null)
@@ -95,9 +97,10 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
/// The local handling method of the rpc.
/// The value indicating whether the rpc should be sent immediately.
/// The parameter to get the target client from, if any.
+ /// Whether to send the RPC only to the host.
[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, string? targetParam = null)
- : this(plugin, method, id, option, localHandling, targetParam)
+ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately, string? targetParam = null, bool hostOnly = false)
+ : this(plugin, method, id, option, localHandling, targetParam, hostOnly)
{
SendImmediately = sendImmediately;
}
@@ -112,6 +115,11 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
///
public string? TargetParameter { get; }
+ ///
+ /// Gets a value indicating whether the RPC is only sent to the host.
+ ///
+ public bool HostOnly { get; }
+
///
protected internal override bool IsSingleton => false;
@@ -200,7 +208,7 @@ DynamicMethod GenerateSender()
}
var targetClientIndex = -1;
- if (TargetParameter != null)
+ if (!HostOnly && TargetParameter != null)
{
targetClientIndex = Array.FindIndex(parameters, p => p.Name == TargetParameter);
}
@@ -260,7 +268,12 @@ DynamicMethod GenerateSender()
il.Emit(OpCodes.Stelem_Ref);
}
- if (targetClientIndex >= 0)
+ if (HostOnly)
+ {
+ il.Emit(OpCodes.Call, typeof(AmongUsClient).GetProperty(nameof(AmongUsClient.Instance), BindingFlags.Static | BindingFlags.Public)!.GetMethod!);
+ il.Emit(OpCodes.Call, typeof(InnerNetClient).GetProperty(nameof(InnerNetClient.HostId), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!);
+ }
+ else if (targetClientIndex >= 0)
{
il.Emit(OpCodes.Ldarg, targetClientIndex + (isStatic ? 0 : 1));
From fd2250ab4c1ee481524731c310cdf837beec51d7 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 20:27:57 -0600
Subject: [PATCH 12/17] Changed to throw exception if defined as both host only
and to have a target
---
Reactor/Networking/Rpc/MethodRpc.cs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index 60efb5e5..d7c6af16 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -70,8 +70,13 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
InnerNetObjectType = method.DeclaringType;
}
- if (!HostOnly && TargetParameter != null)
+ if (TargetParameter != null)
{
+ if (HostOnly)
+ {
+ throw new ArgumentException("Cannot define rpc both addressed to host and define a Target Client.");
+ }
+
var param = Array.Find(parameters, p => p.Name == TargetParameter);
if (param == null)
{
From c4419f9a1cd034fe823c7d7f49e966d9af3ad8ee Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 20:39:36 -0600
Subject: [PATCH 13/17] Added example of Host-only rpc
---
Reactor.Example/CustomRpcs.cs | 1 +
Reactor.Example/ExamplePlugin.cs | 6 ++++++
2 files changed, 7 insertions(+)
diff --git a/Reactor.Example/CustomRpcs.cs b/Reactor.Example/CustomRpcs.cs
index 637bd6fa..64aea25b 100644
--- a/Reactor.Example/CustomRpcs.cs
+++ b/Reactor.Example/CustomRpcs.cs
@@ -5,4 +5,5 @@ public enum CustomRpcCalls : uint
Example,
MethodRpcExample,
TargetRpcExample,
+ HostRpcExample,
}
diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs
index 3331340c..fabb4687 100644
--- a/Reactor.Example/ExamplePlugin.cs
+++ b/Reactor.Example/ExamplePlugin.cs
@@ -105,4 +105,10 @@ public static void RpcSayTo(PlayerControl player, PlayerControl target, string t
{
Logger.Info($"{player.Data.PlayerName} text: {text} sentOnlyTo: {target.NetId}");
}
+
+ [MethodRpc((uint) CustomRpcCalls.HostRpcExample, true)]
+ public static void RpcSayToHost(PlayerControl player, string text)
+ {
+ Logger.Info($"{player.Data.PlayerName} text: {text} amHost: {AmongUsClient.Instance.AmHost} (should always be true)");
+ }
}
From 65337ea3efcdfefe6ee893bc930beb439107181c Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 20:40:57 -0600
Subject: [PATCH 14/17] Returned default constructor for clarity
---
Reactor/Networking/Attributes/MethodRpcAttribute.cs | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Reactor/Networking/Attributes/MethodRpcAttribute.cs b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
index 8a11f81c..e414b205 100644
--- a/Reactor/Networking/Attributes/MethodRpcAttribute.cs
+++ b/Reactor/Networking/Attributes/MethodRpcAttribute.cs
@@ -54,7 +54,7 @@ public sealed class MethodRpcAttribute : Attribute
///
/// The id of the rpc.
/// The parameter to get the target client from, if any.
- public MethodRpcAttribute(uint id, string? targetParam = null)
+ public MethodRpcAttribute(uint id, string? targetParam)
{
Id = id;
TargetParameter = targetParam;
@@ -73,6 +73,17 @@ public MethodRpcAttribute(uint id, bool hostOnly)
HostOnly = hostOnly;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The id of the rpc.
+ public MethodRpcAttribute(uint id)
+ {
+ Id = id;
+ TargetParameter = null;
+ HostOnly = false;
+ }
+
///
/// Registers all method rpc's annotated with in the specified .
///
From 748ee9d9cc6c2a49b55c02f5c6a0c9b65e0ced2e Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 20:56:06 -0600
Subject: [PATCH 15/17] oopsie I forgored something
---
Reactor/Networking/Rpc/MethodRpc.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index d7c6af16..85b605e0 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -275,8 +275,8 @@ DynamicMethod GenerateSender()
if (HostOnly)
{
- il.Emit(OpCodes.Call, typeof(AmongUsClient).GetProperty(nameof(AmongUsClient.Instance), BindingFlags.Static | BindingFlags.Public)!.GetMethod!);
- il.Emit(OpCodes.Call, typeof(InnerNetClient).GetProperty(nameof(InnerNetClient.HostId), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!);
+ il.Emit(OpCodes.Callvirt, typeof(AmongUsClient).GetProperty(nameof(AmongUsClient.Instance), BindingFlags.Static | BindingFlags.Public)!.GetMethod!);
+ il.Emit(OpCodes.Callvirt, typeof(InnerNetClient).GetProperty(nameof(InnerNetClient.HostId), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!);
}
else if (targetClientIndex >= 0)
{
From 678f8d70c8e77f0af2caa1fabcf7b70bb7f1e9da Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Wed, 30 Sep 2026 21:00:00 -0600
Subject: [PATCH 16/17] Curse reflection forever
---
Reactor/Networking/Rpc/MethodRpc.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index 85b605e0..f720b5e3 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -275,7 +275,7 @@ DynamicMethod GenerateSender()
if (HostOnly)
{
- il.Emit(OpCodes.Callvirt, typeof(AmongUsClient).GetProperty(nameof(AmongUsClient.Instance), BindingFlags.Static | BindingFlags.Public)!.GetMethod!);
+ il.Emit(OpCodes.Call, typeof(AmongUsClient).GetProperty(nameof(AmongUsClient.Instance), BindingFlags.Static | BindingFlags.Public)!.GetMethod!);
il.Emit(OpCodes.Callvirt, typeof(InnerNetClient).GetProperty(nameof(InnerNetClient.HostId), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!);
}
else if (targetClientIndex >= 0)
From 89099c062cc58f3d32dcb19c5469d553bb31f332 Mon Sep 17 00:00:00 2001
From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com>
Date: Tue, 6 Oct 2026 22:43:27 -0600
Subject: [PATCH 17/17] Changed Boolean statement
Co-authored-by: AS <56699741+AlchlcDvl@users.noreply.github.com>
---
Reactor/Networking/Rpc/MethodRpc.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Reactor/Networking/Rpc/MethodRpc.cs b/Reactor/Networking/Rpc/MethodRpc.cs
index f720b5e3..84e1f836 100644
--- a/Reactor/Networking/Rpc/MethodRpc.cs
+++ b/Reactor/Networking/Rpc/MethodRpc.cs
@@ -82,7 +82,7 @@ public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption optio
{
throw new ArgumentException("Target Client parameter isn't part of method rpc signature.", nameof(method));
}
- if (!typeof(InnerNetObject).IsAssignableFrom(param.ParameterType) ||
+ if (!typeof(InnerNetObject).IsAssignableFrom(param.ParameterType) &&
!typeof(int).IsAssignableFrom(param.ParameterType))
{
throw new ArgumentException("Target Client parameter has to be an InnerNetObject or int", nameof(method));