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));