From 304708abfa6708682414f3d18e3df7a1e12e8691 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 22 Mar 2025 19:30:46 -0500 Subject: [PATCH 01/17] Add custom InnerNetObjects --- Reactor.Example/ExampleInnerNetObject.cs | 40 ++++++ .../Attributes/InnerNetObjectAttribute.cs | 121 ++++++++++++++++++ .../InnerNetObjectPrefabAttribute.cs | 14 ++ Reactor/ReactorPlugin.cs | 1 + Reactor/Utilities/InnerNetObjectManager.cs | 60 +++++++++ 5 files changed, 236 insertions(+) create mode 100644 Reactor.Example/ExampleInnerNetObject.cs create mode 100644 Reactor/Networking/Attributes/InnerNetObjectAttribute.cs create mode 100644 Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs create mode 100644 Reactor/Utilities/InnerNetObjectManager.cs diff --git a/Reactor.Example/ExampleInnerNetObject.cs b/Reactor.Example/ExampleInnerNetObject.cs new file mode 100644 index 0000000..a1b51dc --- /dev/null +++ b/Reactor.Example/ExampleInnerNetObject.cs @@ -0,0 +1,40 @@ +using System; +using Hazel; +using InnerNet; +using Reactor.Networking.Attributes; +using UnityEngine; + +namespace Reactor.Example; + +[InnerNetObject] +public class ExampleInnerNetObject : InnerNetObject +{ + // Method one of retrieving a prefab. + // Be sure to only use one method! + [InnerNetObjectPrefab] + public static InnerNetObject GetPrefabByInnerNetObject() + { + throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!"); + } + + // Method two of retrieving a prefab. + // Be sure to only use one method! + [InnerNetObjectPrefab] + public static GameObject GetPrefabByGameObject() + { + throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!"); + } + + public override void HandleRpc(byte callId, MessageReader reader) + { + } + + public override bool Serialize(MessageWriter writer, bool initialState) + { + return false; + } + + public override void Deserialize(MessageReader reader, bool initialState) + { + } +} diff --git a/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs new file mode 100644 index 0000000..ac36121 --- /dev/null +++ b/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.Unity.IL2CPP; +using HarmonyLib; +using InnerNet; +using UnityEngine; + +namespace Reactor.Networking.Attributes; + +/// +/// Automatically registers a custom . +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class InnerNetObjectAttribute : Attribute +{ + private static readonly HashSet _registeredAssemblies = new(); + + /// + /// Registers all s annotated with in the specified . + /// + /// This is called automatically on plugin assemblies so you probably don't need to call this. + /// The assembly to search. + public static void Register(Assembly assembly) + { + if (_registeredAssemblies.Contains(assembly)) return; + _registeredAssemblies.Add(assembly); + + foreach (var type in assembly.GetTypes()) + { + var attribute = type.GetCustomAttribute(); + + if (attribute != null) + { + if (!type.IsSubclassOf(typeof(InnerNetObject))) + { + throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(InnerNetObjectAttribute)} but doesn't extend {nameof(InnerNetObject)}."); + } + + try + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + var prefabMethod = methods.FirstOrDefault(method => + method.GetCustomAttribute() != null && + (method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject)) && + method.IsStatic); + + if (prefabMethod != null) + { + var prefab = prefabMethod.Invoke(null, null); + + if (prefab == null) + { + Warning($"Failed to register InnerNetObject, prefab return null."); + } + else if (prefab is InnerNetObject netObj) + { + AddInnerNetObject(netObj); + } + else if (prefab is GameObject gameObj) + { + AddInnerNetObject(gameObj); + } + } + else + { + Warning($"Failed to register InnerNetObject, static prefab return method not found."); + } + } + catch (Exception ex) + { + Warning($"Failed to register {type.FullDescription()}: {ex}"); + } + } + } + } + + private static void AddInnerNetObject(InnerNetObject prefab) + { + var innerNetClient = AmongUsClient.Instance; + + // Setup InnerNetObject. + prefab.SpawnId = (uint) innerNetClient.SpawnableObjects.Length; + UnityEngine.Object.DontDestroyOnLoad(prefab); + + // Add InnerNetObject to NonAddressableSpawnableObjects. + var list = innerNetClient.NonAddressableSpawnableObjects.ToList(); + list.Add(prefab); + innerNetClient.NonAddressableSpawnableObjects = list.ToArray(); + + // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() + var list2 = innerNetClient.SpawnableObjects.ToList(); + list2.Add(new()); + innerNetClient.SpawnableObjects = list2.ToArray(); + } + + private static void AddInnerNetObject(GameObject prefab) + { + if (prefab != null) + { + var netObj = prefab.GetComponent(); + if (netObj != null) + { + AddInnerNetObject(netObj); + } + } + } + + internal static void Initialize() + { + // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() + var innerNetClient = AmongUsClient.Instance; + var list2 = innerNetClient.SpawnableObjects.ToList(); + list2.Add(new()); + innerNetClient.SpawnableObjects = list2.ToArray(); + + IL2CPPChainloader.Instance.PluginLoad += (_, assembly, _) => Register(assembly); + } +} diff --git a/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs b/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs new file mode 100644 index 0000000..1916bb1 --- /dev/null +++ b/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs @@ -0,0 +1,14 @@ +using System; +using InnerNet; +using UnityEngine; + +namespace Reactor.Networking.Attributes; + +/// +/// Attribute for Load prefab method for custom . +/// +/// Must be static and return either with a component, or a prefab. +[AttributeUsage(AttributeTargets.Method)] +public sealed class InnerNetObjectPrefabAttribute : Attribute +{ +} diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index df51844..0421dd7 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -69,6 +69,7 @@ public override void Load() ReactorVersionShower.Initialize(); FreeNamePatch.Initialize(); DefaultBundle.Load(); + InnerNetObjectAttribute.Initialize(); SceneManager.add_sceneLoaded((Action) ((scene, _) => { diff --git a/Reactor/Utilities/InnerNetObjectManager.cs b/Reactor/Utilities/InnerNetObjectManager.cs new file mode 100644 index 0000000..0db3038 --- /dev/null +++ b/Reactor/Utilities/InnerNetObjectManager.cs @@ -0,0 +1,60 @@ +using System.Linq; +using InnerNet; + +namespace Reactor.Utilities; + +/// +/// Provides a standard way of managing s. +/// +public static class InnerNetObjectManager +{ + /// + /// Retrieves the prefab for a custom of the specified type . + /// + /// The type of the prefab to retrieve. Must inherit from . + /// The prefab instance of type . + /// + /// This method searches through the AmongUsClient.Instance.NonAddressableSpawnableObjects array + /// to find a prefab of the specified type. If no matching prefab is found, an exception is thrown. + /// + public static InnerNetObject? GetNetObjPrefab() where T : InnerNetObject + { + var prefab = AmongUsClient.Instance.NonAddressableSpawnableObjects + .FirstOrDefault(obj => obj.TryCast() != null); + + return prefab; + } + + /// + /// Spawns a new instance of a of type . + /// + /// The type of the to spawn. Must inherit from . + /// The owner ID for the spawned object. Defaults to -2, which typically means no specific owner. + /// The spawn flags to use when spawning the object. Defaults to . + /// The newly spawned , if prefab is null then it will return null. + public static InnerNetObject? SpawnNewNetObject(int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) where T : InnerNetObject + { + var netObj = GetNetObjPrefab(); + + if (netObj == null) + { + return null; + } + + var netObjSpawn = UnityEngine.Object.Instantiate(netObj); + AmongUsClient.Instance.Spawn(netObjSpawn, ownerId, spawnFlags); + return netObjSpawn; + } + + /// + /// Spawns an existing instance on the network. + /// + /// The type of the being spawned. Must inherit from . + /// The instance to spawn. + /// The owner ID for the spawned object. Defaults to -2, which typically means no specific owner. + /// The spawn flags to use when spawning the object. Defaults to . + public static void SpawnNetObject(this InnerNetObject netObj, int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) where T : InnerNetObject + { + AmongUsClient.Instance.Spawn(netObj, ownerId, spawnFlags); + } +} From dd9796e65870f1e10dbcf1bb29dcb91a41ba5367 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 22 Mar 2025 19:32:34 -0500 Subject: [PATCH 02/17] clean up --- Reactor/Utilities/InnerNetObjectManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Reactor/Utilities/InnerNetObjectManager.cs b/Reactor/Utilities/InnerNetObjectManager.cs index 0db3038..f673e54 100644 --- a/Reactor/Utilities/InnerNetObjectManager.cs +++ b/Reactor/Utilities/InnerNetObjectManager.cs @@ -26,7 +26,7 @@ public static class InnerNetObjectManager } /// - /// Spawns a new instance of a of type . + /// Spawns a new locally and on the network of type . /// /// The type of the to spawn. Must inherit from . /// The owner ID for the spawned object. Defaults to -2, which typically means no specific owner. @@ -49,11 +49,10 @@ public static class InnerNetObjectManager /// /// Spawns an existing instance on the network. /// - /// The type of the being spawned. Must inherit from . /// The instance to spawn. /// The owner ID for the spawned object. Defaults to -2, which typically means no specific owner. /// The spawn flags to use when spawning the object. Defaults to . - public static void SpawnNetObject(this InnerNetObject netObj, int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) where T : InnerNetObject + public static void SpawnNetObject(this InnerNetObject netObj, int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) { AmongUsClient.Instance.Spawn(netObj, ownerId, spawnFlags); } From 335e88dd5864d37b2997a04fb7a8ff3c7655273e Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 22 Mar 2025 19:58:22 -0500 Subject: [PATCH 03/17] add comments --- Reactor.Example/ExampleInnerNetObject.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Reactor.Example/ExampleInnerNetObject.cs b/Reactor.Example/ExampleInnerNetObject.cs index a1b51dc..7282be7 100644 --- a/Reactor.Example/ExampleInnerNetObject.cs +++ b/Reactor.Example/ExampleInnerNetObject.cs @@ -25,15 +25,18 @@ public static GameObject GetPrefabByGameObject() throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!"); } + // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. public override void HandleRpc(byte callId, MessageReader reader) { } + // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. public override bool Serialize(MessageWriter writer, bool initialState) { return false; } + // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. public override void Deserialize(MessageReader reader, bool initialState) { } From ca56992e4bbb03e680dd9f32f67154567fd5e6e5 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 22 Mar 2025 20:28:42 -0500 Subject: [PATCH 04/17] fix AmongUsClient.Instance being null by adding lateload --- Reactor/Patches/Miscellaneous/LateLoadPatch.cs | 15 +++++++++++++++ Reactor/ReactorPlugin.cs | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 Reactor/Patches/Miscellaneous/LateLoadPatch.cs diff --git a/Reactor/Patches/Miscellaneous/LateLoadPatch.cs b/Reactor/Patches/Miscellaneous/LateLoadPatch.cs new file mode 100644 index 0000000..a2d3043 --- /dev/null +++ b/Reactor/Patches/Miscellaneous/LateLoadPatch.cs @@ -0,0 +1,15 @@ +using HarmonyLib; +using Reactor.Utilities; + +namespace Reactor.Patches.Miscellaneous; + +[HarmonyPatch] +internal static class LateLoadPatch +{ + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.Awake))] + [HarmonyPostfix] + public static void Awake_Postfix() + { + PluginSingleton.Instance.LateLoad(); + } +} diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 0421dd7..eb1a984 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -69,7 +69,6 @@ public override void Load() ReactorVersionShower.Initialize(); FreeNamePatch.Initialize(); DefaultBundle.Load(); - InnerNetObjectAttribute.Initialize(); SceneManager.add_sceneLoaded((Action) ((scene, _) => { @@ -80,6 +79,12 @@ public override void Load() })); } + /// + public void LateLoad() + { + InnerNetObjectAttribute.Initialize(); + } + /// public override bool Unload() { From 962185fd085f71e670dcb21aba359f9dcd13b3a3 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sat, 22 Mar 2025 21:02:42 -0500 Subject: [PATCH 05/17] Final Fix --- .../Attributes/InnerNetObjectAttribute.cs | 51 +++++++++++-------- Reactor/ReactorPlugin.cs | 3 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs index ac36121..f77e29a 100644 --- a/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs @@ -16,6 +16,7 @@ namespace Reactor.Networking.Attributes; public sealed class InnerNetObjectAttribute : Attribute { private static readonly HashSet _registeredAssemblies = new(); + private static readonly HashSet _registeredMethods = new(); /// /// Registers all s annotated with in the specified . @@ -49,20 +50,7 @@ public static void Register(Assembly assembly) if (prefabMethod != null) { - var prefab = prefabMethod.Invoke(null, null); - - if (prefab == null) - { - Warning($"Failed to register InnerNetObject, prefab return null."); - } - else if (prefab is InnerNetObject netObj) - { - AddInnerNetObject(netObj); - } - else if (prefab is GameObject gameObj) - { - AddInnerNetObject(gameObj); - } + _registeredMethods.Add(prefabMethod); } else { @@ -77,6 +65,35 @@ public static void Register(Assembly assembly) } } + internal static void LoadRegistered() + { + if (_registeredMethods.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() + { + var innerNetClient = AmongUsClient.Instance; + var list2 = innerNetClient.SpawnableObjects.ToList(); + list2.Add(new()); + innerNetClient.SpawnableObjects = list2.ToArray(); + } + + foreach (var prefabMethod in _registeredMethods) + { + var prefab = prefabMethod.Invoke(null, null); + + if (prefab == null) + { + Warning($"Failed to register InnerNetObject, prefab return null."); + } + else if (prefab is InnerNetObject netObj) + { + AddInnerNetObject(netObj); + } + else if (prefab is GameObject gameObj) + { + AddInnerNetObject(gameObj); + } + } + } + private static void AddInnerNetObject(InnerNetObject prefab) { var innerNetClient = AmongUsClient.Instance; @@ -110,12 +127,6 @@ private static void AddInnerNetObject(GameObject prefab) internal static void Initialize() { - // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() - var innerNetClient = AmongUsClient.Instance; - var list2 = innerNetClient.SpawnableObjects.ToList(); - list2.Add(new()); - innerNetClient.SpawnableObjects = list2.ToArray(); - IL2CPPChainloader.Instance.PluginLoad += (_, assembly, _) => Register(assembly); } } diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index eb1a984..45a067b 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -51,6 +51,7 @@ public ReactorPlugin() RegisterCustomRpcAttribute.Initialize(); MessageConverterAttribute.Initialize(); MethodRpcAttribute.Initialize(); + InnerNetObjectAttribute.Initialize(); LocalizationManager.Register(new HardCodedLocalizationProvider()); } @@ -82,7 +83,7 @@ public override void Load() /// public void LateLoad() { - InnerNetObjectAttribute.Initialize(); + InnerNetObjectAttribute.LoadRegistered(); } /// From 805cdbc2a71c2e8939e345d632cbb9f00330f456 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 07:10:39 -0500 Subject: [PATCH 06/17] Log if host --- Reactor/Utilities/InnerNetObjectManager.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Reactor/Utilities/InnerNetObjectManager.cs b/Reactor/Utilities/InnerNetObjectManager.cs index f673e54..efb9368 100644 --- a/Reactor/Utilities/InnerNetObjectManager.cs +++ b/Reactor/Utilities/InnerNetObjectManager.cs @@ -34,6 +34,12 @@ public static class InnerNetObjectManager /// The newly spawned , if prefab is null then it will return null. public static InnerNetObject? SpawnNewNetObject(int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) where T : InnerNetObject { + if (!AmongUsClient.Instance.AmHost) + { + Warning("You can only spawn a InnerNetObject as Host."); + return null; + } + var netObj = GetNetObjPrefab(); if (netObj == null) @@ -54,6 +60,12 @@ public static class InnerNetObjectManager /// The spawn flags to use when spawning the object. Defaults to . public static void SpawnNetObject(this InnerNetObject netObj, int ownerId = -2, SpawnFlags spawnFlags = SpawnFlags.None) { + if (!AmongUsClient.Instance.AmHost) + { + Warning("You can only spawn a InnerNetObject as Host."); + return; + } + AmongUsClient.Instance.Spawn(netObj, ownerId, spawnFlags); } } From f6be7b8f6ef9b36a4cc3d2eb2f529771a02d11c0 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 13:06:49 -0500 Subject: [PATCH 07/17] Updated example. Replaced InnerNetObjectAttribute with IgnoreInnerNetObjectAttribute, now automatically registers InnerNetObjects. Added more methods to load prefab. --- Reactor.Example/ExampleInnerNetObject.cs | 50 +++++++--- ...te.cs => IgnoreInnerNetObjectAttribute.cs} | 91 ++++++++++++++----- .../InnerNetObjectPrefabAttribute.cs | 2 +- Reactor/ReactorPlugin.cs | 4 +- 4 files changed, 109 insertions(+), 38 deletions(-) rename Reactor/Networking/Attributes/{InnerNetObjectAttribute.cs => IgnoreInnerNetObjectAttribute.cs} (52%) diff --git a/Reactor.Example/ExampleInnerNetObject.cs b/Reactor.Example/ExampleInnerNetObject.cs index 7282be7..f1b7842 100644 --- a/Reactor.Example/ExampleInnerNetObject.cs +++ b/Reactor.Example/ExampleInnerNetObject.cs @@ -1,43 +1,71 @@ using System; +using System.Threading.Tasks; using Hazel; using InnerNet; using Reactor.Networking.Attributes; -using UnityEngine; namespace Reactor.Example; -[InnerNetObject] +// The `IgnoreInnerNetObject` attribute is used to prevent this custom InnerNetObject +// from being automatically registered by Reactor. +[IgnoreInnerNetObject] public class ExampleInnerNetObject : InnerNetObject { - // Method one of retrieving a prefab. - // Be sure to only use one method! + // The `InnerNetObjectPrefab` attribute is used to define how the prefab for this + // custom InnerNetObject is retrieved. The prefab is a template object that is used + // when spawning instances of this object in the game. + // There are three examples provided for retrieving the prefab: + + // Example 1: Directly assign a prefab + // This is the simplest method, where the prefab is assigned directly to a static field or property. + [InnerNetObjectPrefab] + public static InnerNetObject? PrefabField; + // or + [InnerNetObjectPrefab] + public static InnerNetObject? PrefabProperty { get; set; } + + // Example 2: Retrieve the prefab via a static method + // This method allows for more complex logic to retrieve the prefab. + // The method must be static and return an InnerNetObject (or a GameObject with an InnerNetObject component). [InnerNetObjectPrefab] - public static InnerNetObject GetPrefabByInnerNetObject() + public static InnerNetObject GetPrefab() { throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!"); } - // Method two of retrieving a prefab. - // Be sure to only use one method! + // Example 3: Retrieve the prefab asynchronously + // This method is similar to Example 2 but allows for asynchronous operations, + // such as loading assets from disk. [InnerNetObjectPrefab] - public static GameObject GetPrefabByGameObject() + public static async Task GetPrefabAsync() { throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!"); } - // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. + // The `HandleRpc` method is required abstract to handle Remote Procedure Calls (RPCs) for this object. + // RPCs are used to communicate between clients and the server. + // The `callId` parameter identifies the type of RPC, and the `reader` parameter provides the data. public override void HandleRpc(byte callId, MessageReader reader) { + // Implement logic to handle specific RPCs based on the `callId`. + // For example, you might switch on `callId` to handle different types of RPCs. } - // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. + // The `Serialize` method is required abstract to serialize the state of this object into a `MessageWriter`. + // This is used to synchronize the object's state across the network. + // The `initialState` parameter indicates whether this is the first time the object is being serialized. public override bool Serialize(MessageWriter writer, bool initialState) { + // Implement logic to write the object's state to the `writer`. + // Return `true` if the state was serialized successfully, otherwise `false`. return false; } - // Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown. + // The `Deserialize` method is required abstract to deserialize the state of this object from a `MessageReader`. + // This is used to update the object's state based on data received from the network. + // The `initialState` parameter indicates whether this is the first time the object is being deserialized. public override void Deserialize(MessageReader reader, bool initialState) { + // Implement logic to read the object's state from the `reader`. } } diff --git a/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs similarity index 52% rename from Reactor/Networking/Attributes/InnerNetObjectAttribute.cs rename to Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index f77e29a..a9d5c52 100644 --- a/Reactor/Networking/Attributes/InnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using BepInEx.Unity.IL2CPP; using HarmonyLib; using InnerNet; @@ -10,16 +11,16 @@ namespace Reactor.Networking.Attributes; /// -/// Automatically registers a custom . +/// Ignores registering . /// [AttributeUsage(AttributeTargets.Class)] -public sealed class InnerNetObjectAttribute : Attribute +public sealed class IgnoreInnerNetObjectAttribute : Attribute { private static readonly HashSet _registeredAssemblies = new(); - private static readonly HashSet _registeredMethods = new(); + private static readonly HashSet _registeredMembers = new(); /// - /// Registers all s annotated with in the specified . + /// Registers all s annotated with out in the specified . /// /// This is called automatically on plugin assemblies so you probably don't need to call this. /// The assembly to search. @@ -30,31 +31,41 @@ public static void Register(Assembly assembly) foreach (var type in assembly.GetTypes()) { - var attribute = type.GetCustomAttribute(); + if (!type.IsSubclassOf(typeof(InnerNetObject))) continue; - if (attribute != null) - { - if (!type.IsSubclassOf(typeof(InnerNetObject))) - { - throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(InnerNetObjectAttribute)} but doesn't extend {nameof(InnerNetObject)}."); - } + var attribute = type.GetCustomAttribute(); + if (attribute == null) + { try { - var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - - var prefabMethod = methods.FirstOrDefault(method => - method.GetCustomAttribute() != null && - (method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject)) && - method.IsStatic); + var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - if (prefabMethod != null) + var prefabMember = members.FirstOrDefault(member => { - _registeredMethods.Add(prefabMethod); + if (member is MethodInfo method) + { + return method.IsStatic && (method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject) || method.ReturnType == typeof(Task) || method.ReturnType == typeof(Task)); + } + else if (member is FieldInfo field) + { + return field.IsStatic && (field.FieldType == typeof(GameObject) || field.FieldType == typeof(InnerNetObject)); + } + else if (member is PropertyInfo property) + { + return property.GetMethod?.IsStatic == true && (property.PropertyType == typeof(GameObject) || property.PropertyType == typeof(InnerNetObject)); + } + + return false; + }); + + if (prefabMember != null) + { + _registeredMembers.Add(prefabMember); } else { - Warning($"Failed to register InnerNetObject, static prefab return method not found."); + Warning($"Failed to register InnerNetObject, static prefab return member not found."); } } catch (Exception ex) @@ -65,9 +76,9 @@ public static void Register(Assembly assembly) } } - internal static void LoadRegistered() + internal static async Task LoadRegisteredAsync() { - if (_registeredMethods.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() + if (_registeredMembers.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() { var innerNetClient = AmongUsClient.Instance; var list2 = innerNetClient.SpawnableObjects.ToList(); @@ -75,9 +86,41 @@ internal static void LoadRegistered() innerNetClient.SpawnableObjects = list2.ToArray(); } - foreach (var prefabMethod in _registeredMethods) + foreach (var prefabMember in _registeredMembers) { - var prefab = prefabMethod.Invoke(null, null); + object? prefab = null; + + if (prefabMember is MethodInfo method) + { + if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(Task)) + { + if (method.Invoke(null, null) is Task task) + { + await task.ConfigureAwait(false); + + if (method.ReturnType == typeof(Task)) + { + prefab = ((Task) task).Result; + } + else if (method.ReturnType == typeof(Task)) + { + prefab = ((Task) task).Result; + } + } + } + else + { + prefab = method.Invoke(null, null); + } + } + else if (prefabMember is FieldInfo field) + { + prefab = field.GetValue(null); + } + else if (prefabMember is PropertyInfo property) + { + prefab = property.GetValue(null); + } if (prefab == null) { diff --git a/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs b/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs index 1916bb1..efc0e25 100644 --- a/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs +++ b/Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs @@ -8,7 +8,7 @@ namespace Reactor.Networking.Attributes; /// Attribute for Load prefab method for custom . /// /// Must be static and return either with a component, or a prefab. -[AttributeUsage(AttributeTargets.Method)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public sealed class InnerNetObjectPrefabAttribute : Attribute { } diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 45a067b..381400c 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -51,7 +51,7 @@ public ReactorPlugin() RegisterCustomRpcAttribute.Initialize(); MessageConverterAttribute.Initialize(); MethodRpcAttribute.Initialize(); - InnerNetObjectAttribute.Initialize(); + IgnoreInnerNetObjectAttribute.Initialize(); LocalizationManager.Register(new HardCodedLocalizationProvider()); } @@ -83,7 +83,7 @@ public override void Load() /// public void LateLoad() { - InnerNetObjectAttribute.LoadRegistered(); + _ = IgnoreInnerNetObjectAttribute.LoadRegisteredAsync(); } /// From 0b12ae6c7099e1e50743069c14129ada59ab7d30 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 13:10:57 -0500 Subject: [PATCH 08/17] update info --- Reactor.Example/ExampleInnerNetObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Reactor.Example/ExampleInnerNetObject.cs b/Reactor.Example/ExampleInnerNetObject.cs index f1b7842..f09b11b 100644 --- a/Reactor.Example/ExampleInnerNetObject.cs +++ b/Reactor.Example/ExampleInnerNetObject.cs @@ -14,6 +14,7 @@ public class ExampleInnerNetObject : InnerNetObject // The `InnerNetObjectPrefab` attribute is used to define how the prefab for this // custom InnerNetObject is retrieved. The prefab is a template object that is used // when spawning instances of this object in the game. + // The return type can either be a InnerNetObject, or GameObject but must have a InnerNetObject component attached. // There are three examples provided for retrieving the prefab: // Example 1: Directly assign a prefab From de0145c07c5c413a8f3b2b99c2ce32a804efae0b Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 15:43:16 -0500 Subject: [PATCH 09/17] Add Enum and EnumFlag argument support to MessageSerializer --- Reactor/Networking/Serialization/MessageSerializer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Reactor/Networking/Serialization/MessageSerializer.cs b/Reactor/Networking/Serialization/MessageSerializer.cs index 3f92591..6235dd5 100644 --- a/Reactor/Networking/Serialization/MessageSerializer.cs +++ b/Reactor/Networking/Serialization/MessageSerializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Hazel; using Reactor.Networking.Extensions; @@ -90,6 +91,9 @@ public static void Serialize(this MessageWriter writer, object @object) case Vector2 i: writer.Write(i); break; + case Enum i: + writer.WritePacked(Convert.ToInt32(i, CultureInfo.InvariantCulture)); + break; case string i: writer.Write(i); break; @@ -153,6 +157,11 @@ public static object Deserialize(this MessageReader reader, Type objectType) return reader.ReadVector2(); } + if (objectType.IsEnum) + { + return Enum.ToObject(objectType, reader.ReadPackedInt32()); + } + if (objectType == typeof(string)) { return reader.ReadString(); From 252e88433212c0ea14999477ea4c115abcd94f8a Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 16:18:51 -0500 Subject: [PATCH 10/17] Handle each type individually --- .../Serialization/MessageSerializer.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Reactor/Networking/Serialization/MessageSerializer.cs b/Reactor/Networking/Serialization/MessageSerializer.cs index 6235dd5..2e8cc41 100644 --- a/Reactor/Networking/Serialization/MessageSerializer.cs +++ b/Reactor/Networking/Serialization/MessageSerializer.cs @@ -91,12 +91,13 @@ public static void Serialize(this MessageWriter writer, object @object) case Vector2 i: writer.Write(i); break; - case Enum i: - writer.WritePacked(Convert.ToInt32(i, CultureInfo.InvariantCulture)); - break; case string i: writer.Write(i); break; + case Enum i: + var underlyingValue = Convert.ChangeType(i, Enum.GetUnderlyingType(i.GetType()), CultureInfo.InvariantCulture); + Serialize(writer, underlyingValue); + break; default: var converter = FindConverter(@object.GetType()); if (converter != null) @@ -157,14 +158,16 @@ public static object Deserialize(this MessageReader reader, Type objectType) return reader.ReadVector2(); } - if (objectType.IsEnum) + if (objectType == typeof(string)) { - return Enum.ToObject(objectType, reader.ReadPackedInt32()); + return reader.ReadString(); } - if (objectType == typeof(string)) + if (objectType.IsEnum) { - return reader.ReadString(); + var underlyingType = Enum.GetUnderlyingType(objectType); + var underlyingValue = Deserialize(reader, underlyingType); + return Enum.ToObject(objectType, underlyingValue); } var converter = FindConverter(objectType); From 51139e093eebd1f4d0427d68a4b39cedd22d9ffa Mon Sep 17 00:00:00 2001 From: D1GQ Date: Sun, 23 Mar 2025 16:52:52 -0500 Subject: [PATCH 11/17] Remove Enum for MessageSerializer, is in another pr --- .../Networking/Serialization/MessageSerializer.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Reactor/Networking/Serialization/MessageSerializer.cs b/Reactor/Networking/Serialization/MessageSerializer.cs index 2e8cc41..3f92591 100644 --- a/Reactor/Networking/Serialization/MessageSerializer.cs +++ b/Reactor/Networking/Serialization/MessageSerializer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Hazel; using Reactor.Networking.Extensions; @@ -94,10 +93,6 @@ public static void Serialize(this MessageWriter writer, object @object) case string i: writer.Write(i); break; - case Enum i: - var underlyingValue = Convert.ChangeType(i, Enum.GetUnderlyingType(i.GetType()), CultureInfo.InvariantCulture); - Serialize(writer, underlyingValue); - break; default: var converter = FindConverter(@object.GetType()); if (converter != null) @@ -163,13 +158,6 @@ public static object Deserialize(this MessageReader reader, Type objectType) return reader.ReadString(); } - if (objectType.IsEnum) - { - var underlyingType = Enum.GetUnderlyingType(objectType); - var underlyingValue = Deserialize(reader, underlyingType); - return Enum.ToObject(objectType, underlyingValue); - } - var converter = FindConverter(objectType); if (converter != null) { From 38a28afcebc017ccb2f0208ea4de31d82e104914 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Mon, 24 Mar 2025 19:18:36 -0500 Subject: [PATCH 12/17] Load in alphabetical order --- .../IgnoreInnerNetObjectAttribute.cs | 2 +- Reactor/Utilities/InnerNetObjectManager.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index a9d5c52..61fc56f 100644 --- a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -86,7 +86,7 @@ internal static async Task LoadRegisteredAsync() innerNetClient.SpawnableObjects = list2.ToArray(); } - foreach (var prefabMember in _registeredMembers) + foreach (var prefabMember in _registeredMembers.OrderBy(member => member.DeclaringType?.FullName)) { object? prefab = null; diff --git a/Reactor/Utilities/InnerNetObjectManager.cs b/Reactor/Utilities/InnerNetObjectManager.cs index efb9368..1f86490 100644 --- a/Reactor/Utilities/InnerNetObjectManager.cs +++ b/Reactor/Utilities/InnerNetObjectManager.cs @@ -25,6 +25,26 @@ public static class InnerNetObjectManager return prefab; } + /// + /// Retrieves the spwan id from the prefab of the specified type . + /// + /// The type of the prefab to retrieve. Must inherit from . + /// The spawn id of type , if prefab not found then 0. + /// + /// This method searches through the AmongUsClient.Instance.NonAddressableSpawnableObjects array + /// to find the spwan id of a prefab. + /// + public static uint GetSpawnId() where T : InnerNetObject + { + var prefab = GetNetObjPrefab(); + if (prefab != null) + { + return prefab.SpawnId; + } + + return 0; + } + /// /// Spawns a new locally and on the network of type . /// From 50c8f84c33c64b5084b8aa66f80e0fd6b480de77 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Tue, 25 Mar 2025 13:08:05 -0500 Subject: [PATCH 13/17] Remove lateload. load in the same order no matter assembly order --- .../IgnoreInnerNetObjectAttribute.cs | 166 +++++++++--------- .../Patches/Miscellaneous/LateLoadPatch.cs | 15 -- Reactor/Reactor.csproj | 4 + Reactor/ReactorPlugin.cs | 6 - 4 files changed, 88 insertions(+), 103 deletions(-) delete mode 100644 Reactor/Patches/Miscellaneous/LateLoadPatch.cs diff --git a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index 61fc56f..2e8e632 100644 --- a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Threading.Tasks; using BepInEx.Unity.IL2CPP; -using HarmonyLib; using InnerNet; using UnityEngine; @@ -17,7 +16,7 @@ namespace Reactor.Networking.Attributes; public sealed class IgnoreInnerNetObjectAttribute : Attribute { private static readonly HashSet _registeredAssemblies = new(); - private static readonly HashSet _registeredMembers = new(); + private static readonly List<(string AssemblyName, MemberInfo Member)> _registeredMembers = new(); /// /// Registers all s annotated with out in the specified . @@ -29,112 +28,114 @@ public static void Register(Assembly assembly) if (_registeredAssemblies.Contains(assembly)) return; _registeredAssemblies.Add(assembly); + var assemblyName = assembly.GetName().Name; + foreach (var type in assembly.GetTypes()) { if (!type.IsSubclassOf(typeof(InnerNetObject))) continue; + if (type.GetCustomAttribute() != null) continue; - var attribute = type.GetCustomAttribute(); - - if (attribute == null) + try { - try + var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + var prefabMember = members.FirstOrDefault(IsValidPrefabMember); + + if (prefabMember != null && assemblyName != null) { - var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - - var prefabMember = members.FirstOrDefault(member => - { - if (member is MethodInfo method) - { - return method.IsStatic && (method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject) || method.ReturnType == typeof(Task) || method.ReturnType == typeof(Task)); - } - else if (member is FieldInfo field) - { - return field.IsStatic && (field.FieldType == typeof(GameObject) || field.FieldType == typeof(InnerNetObject)); - } - else if (member is PropertyInfo property) - { - return property.GetMethod?.IsStatic == true && (property.PropertyType == typeof(GameObject) || property.PropertyType == typeof(InnerNetObject)); - } - - return false; - }); - - if (prefabMember != null) - { - _registeredMembers.Add(prefabMember); - } - else - { - Warning($"Failed to register InnerNetObject, static prefab return member not found."); - } + _registeredMembers.Add((assemblyName, prefabMember)); } - catch (Exception ex) + else { - Warning($"Failed to register {type.FullDescription()}: {ex}"); + Warning($"No valid prefab member found for {type.FullName} in {assemblyName}."); } } + catch (Exception ex) + { + Warning($"Failed to register {type.FullName}: {ex}"); + } } } - internal static async Task LoadRegisteredAsync() + private static bool IsValidPrefabMember(MemberInfo member) { - if (_registeredMembers.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn() + return member switch { - var innerNetClient = AmongUsClient.Instance; - var list2 = innerNetClient.SpawnableObjects.ToList(); - list2.Add(new()); - innerNetClient.SpawnableObjects = list2.ToArray(); - } + MethodInfo method => method.IsStatic && ( + method.ReturnType == typeof(GameObject) || + method.ReturnType == typeof(InnerNetObject) || + method.ReturnType == typeof(Task) || + method.ReturnType == typeof(Task) + ), + FieldInfo field => field.IsStatic && ( + field.FieldType == typeof(GameObject) || + field.FieldType == typeof(InnerNetObject) + ), + PropertyInfo property => property.GetMethod?.IsStatic == true && ( + property.PropertyType == typeof(GameObject) || + property.PropertyType == typeof(InnerNetObject) + ), + _ => false, + }; + } - foreach (var prefabMember in _registeredMembers.OrderBy(member => member.DeclaringType?.FullName)) - { - object? prefab = null; + internal static async Task LoadRegisteredAsync() + { + while (AmongUsClient.Instance == null) + await Task.Delay(1000); - if (prefabMember is MethodInfo method) - { - if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(Task)) - { - if (method.Invoke(null, null) is Task task) - { - await task.ConfigureAwait(false); - - if (method.ReturnType == typeof(Task)) - { - prefab = ((Task) task).Result; - } - else if (method.ReturnType == typeof(Task)) - { - prefab = ((Task) task).Result; - } - } - } - else - { - prefab = method.Invoke(null, null); - } - } - else if (prefabMember is FieldInfo field) - { - prefab = field.GetValue(null); - } - else if (prefabMember is PropertyInfo property) - { - prefab = property.GetValue(null); - } + var orderedMembers = _registeredMembers + .OrderBy(x => x.AssemblyName) + .ThenBy(x => x.Member.DeclaringType?.FullName) + .ThenBy(x => x.Member.Name) + .Select(x => x.Member); + foreach (var prefabMember in orderedMembers) + { + var prefab = await GetPrefabAsync(prefabMember); if (prefab == null) { - Warning($"Failed to register InnerNetObject, prefab return null."); + Warning($"Prefab for {prefabMember.DeclaringType?.FullName}.{prefabMember.Name} is null."); + continue; } - else if (prefab is InnerNetObject netObj) - { + + if (prefab is InnerNetObject netObj) AddInnerNetObject(netObj); - } else if (prefab is GameObject gameObj) - { AddInnerNetObject(gameObj); + } + } + + private static async Task GetPrefabAsync(MemberInfo prefabMember) + { + object? prefab = null; + + if (prefabMember is MethodInfo method) + { + if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(Task)) + { + if (method.Invoke(null, null) is Task task) + { + await task.ConfigureAwait(false); + prefab = method.ReturnType == typeof(Task) + ? ((Task) task).Result + : ((Task) task).Result; + } + } + else + { + prefab = method.Invoke(null, null); } } + else if (prefabMember is FieldInfo field) + { + prefab = field.GetValue(null); + } + else if (prefabMember is PropertyInfo property) + { + prefab = property.GetValue(null); + } + + return prefab; } private static void AddInnerNetObject(InnerNetObject prefab) @@ -171,5 +172,6 @@ private static void AddInnerNetObject(GameObject prefab) internal static void Initialize() { IL2CPPChainloader.Instance.PluginLoad += (_, assembly, _) => Register(assembly); + IL2CPPChainloader.Instance.Finished += () => _ = LoadRegisteredAsync(); } } diff --git a/Reactor/Patches/Miscellaneous/LateLoadPatch.cs b/Reactor/Patches/Miscellaneous/LateLoadPatch.cs deleted file mode 100644 index a2d3043..0000000 --- a/Reactor/Patches/Miscellaneous/LateLoadPatch.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HarmonyLib; -using Reactor.Utilities; - -namespace Reactor.Patches.Miscellaneous; - -[HarmonyPatch] -internal static class LateLoadPatch -{ - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.Awake))] - [HarmonyPostfix] - public static void Awake_Postfix() - { - PluginSingleton.Instance.LateLoad(); - } -} diff --git a/Reactor/Reactor.csproj b/Reactor/Reactor.csproj index 7e09fec..dd80d3c 100644 --- a/Reactor/Reactor.csproj +++ b/Reactor/Reactor.csproj @@ -16,5 +16,9 @@ + + + + diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 381400c..45f93d8 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -80,12 +80,6 @@ public override void Load() })); } - /// - public void LateLoad() - { - _ = IgnoreInnerNetObjectAttribute.LoadRegisteredAsync(); - } - /// public override bool Unload() { From 6f0732add84c21c5f502a86361db8c8dae75f7d6 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Tue, 25 Mar 2025 15:08:02 -0500 Subject: [PATCH 14/17] oops --- Reactor/Reactor.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Reactor/Reactor.csproj b/Reactor/Reactor.csproj index dd80d3c..7e09fec 100644 --- a/Reactor/Reactor.csproj +++ b/Reactor/Reactor.csproj @@ -16,9 +16,5 @@ - - - - From 7e93c4e566a44deb4f2e262074182ccd5f0e5817 Mon Sep 17 00:00:00 2001 From: D1GQ <83262852+D1GQ@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:41:39 -0500 Subject: [PATCH 15/17] Update Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs Co-authored-by: miniduikboot --- Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index 2e8e632..22eb56e 100644 --- a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -19,7 +19,7 @@ public sealed class IgnoreInnerNetObjectAttribute : Attribute private static readonly List<(string AssemblyName, MemberInfo Member)> _registeredMembers = new(); /// - /// Registers all s annotated with out in the specified . + /// Registers all s annotated without in the specified . /// /// This is called automatically on plugin assemblies so you probably don't need to call this. /// The assembly to search. From 0a3e33c0eadd39148806ed9ae8972435bc61940a Mon Sep 17 00:00:00 2001 From: D1GQ Date: Wed, 9 Jul 2025 21:08:20 -0500 Subject: [PATCH 16/17] Check for duplicates --- .../IgnoreInnerNetObjectAttribute.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index 2e8e632..f0c56cc 100644 --- a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -15,6 +15,7 @@ namespace Reactor.Networking.Attributes; [AttributeUsage(AttributeTargets.Class)] public sealed class IgnoreInnerNetObjectAttribute : Attribute { + private static readonly HashSet _registeredPrefabs = new(); private static readonly HashSet _registeredAssemblies = new(); private static readonly List<(string AssemblyName, MemberInfo Member)> _registeredMembers = new(); @@ -91,17 +92,26 @@ internal static async Task LoadRegisteredAsync() foreach (var prefabMember in orderedMembers) { + var prefabFullName = $"{prefabMember.DeclaringType?.FullName}.{prefabMember.Name}"; var prefab = await GetPrefabAsync(prefabMember); if (prefab == null) { - Warning($"Prefab for {prefabMember.DeclaringType?.FullName}.{prefabMember.Name} is null."); + Warning($"Prefab for {prefabFullName} is null."); continue; } - if (prefab is InnerNetObject netObj) - AddInnerNetObject(netObj); - else if (prefab is GameObject gameObj) - AddInnerNetObject(gameObj); + if (!_registeredPrefabs.Contains(prefabFullName)) + { + _registeredPrefabs.Add(prefabFullName); + if (prefab is InnerNetObject netObj) + AddInnerNetObject(netObj); + else if (prefab is GameObject gameObj) + AddInnerNetObject(gameObj); + } + else + { + Warning($"Prefab {prefabFullName} has already been registered, which shouldn't be possible but indicates there is a duplicate..."); + } } } From 7cdb07890f8c08418b9507f14fd9637823050b1c Mon Sep 17 00:00:00 2001 From: D1GQ Date: Wed, 9 Jul 2025 21:12:01 -0500 Subject: [PATCH 17/17] Cleanup --- .../Networking/Attributes/IgnoreInnerNetObjectAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs index f7e1d77..05c5bbf 100644 --- a/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs +++ b/Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs @@ -103,10 +103,15 @@ internal static async Task LoadRegisteredAsync() if (!_registeredPrefabs.Contains(prefabFullName)) { _registeredPrefabs.Add(prefabFullName); + if (prefab is InnerNetObject netObj) + { AddInnerNetObject(netObj); + } else if (prefab is GameObject gameObj) + { AddInnerNetObject(gameObj); + } } else {