diff --git a/AmongUs.props b/AmongUs.props index 5c84ddf..624e8aa 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -10,7 +10,7 @@ - NuclearPowered/Mappings:0.1.0 + NuclearPowered/Mappings:0.1.1 diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs index 3959961..f64d39e 100644 --- a/Reactor.Example/ExamplePlugin.cs +++ b/Reactor.Example/ExamplePlugin.cs @@ -38,8 +38,8 @@ namespace Reactor.Example if (Input.GetKeyDown(KeyCode.F3) && AmongUsClient.Instance && PlayerControl.LocalPlayer) { Plugin.Log.LogWarning("Sending example rpc"); - PlayerControl.LocalPlayer.Send(new ExampleRpc.Data("Cześć :)")); - PlayerControl.LocalPlayer.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data("host :O")); + Rpc.Instance.Send(new ExampleRpc.Data("Cześć :)")); + Rpc.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data("host :O")); } } } diff --git a/Reactor.Example/ExampleRpc.cs b/Reactor.Example/ExampleRpc.cs index 178bbd6..f95bd45 100644 --- a/Reactor.Example/ExampleRpc.cs +++ b/Reactor.Example/ExampleRpc.cs @@ -3,7 +3,7 @@ using Hazel; namespace Reactor.Example { [RegisterCustomRpc] - public class ExampleRpc : CustomRpc + public class ExampleRpc : PlayerCustomRpc { public ExampleRpc(ExamplePlugin plugin) : base(plugin) { diff --git a/Reactor/CustomRpc.cs b/Reactor/CustomRpc.cs index 7a27694..0e49c4a 100644 --- a/Reactor/CustomRpc.cs +++ b/Reactor/CustomRpc.cs @@ -23,11 +23,13 @@ namespace Reactor { public int? Id { get; internal set; } + internal CustomRpcManager Manager { get; set; } public BasePlugin UnsafePlugin { get; } public string PluginId { get; } public abstract Type InnerNetObjectType { get; } + public virtual SendOption SendOption { get; } = SendOption.Reliable; public abstract RpcLocalHandling LocalHandling { get; } protected UnsafeCustomRpc(BasePlugin plugin) @@ -39,6 +41,54 @@ namespace Reactor public abstract void UnsafeWrite(MessageWriter writer, object data); public abstract object UnsafeRead(MessageReader reader); public abstract void UnsafeHandle(InnerNetObject innerNetObject, object data); + + public void UnsafeSend(InnerNetObject netObject, object data, bool immediately = false, int targetClientId = -1) + { + if (netObject == null) throw new ArgumentNullException(nameof(netObject)); + if (data == null) throw new ArgumentNullException(nameof(data)); + + if (Id == null) + { + throw new InvalidOperationException("Can't send unregistered CustomRpc"); + } + + if (LocalHandling == RpcLocalHandling.Before) + { + UnsafeHandle(netObject, data); + } + + var writer = immediately switch + { + false => AmongUsClient.Instance.StartRpc(netObject.NetId, CustomRpcManager.CallId, SendOption), + true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId) + }; + + if (Manager.PluginIdMap.TryGetValue(PluginId, out var pluginId)) + { + writer.WritePacked(pluginId); + } + else + { + writer.Write(PluginId); + } + + writer.WritePacked(Id!.Value); + UnsafeWrite(writer, data); + + if (immediately) + { + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + else + { + writer.EndMessage(); + } + + if (LocalHandling == RpcLocalHandling.After) + { + UnsafeHandle(netObject, data); + } + } } public abstract class CustomRpc : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject @@ -68,6 +118,33 @@ namespace Reactor { Handle((TInnerNetObject) innerNetObject, (TData) data); } + + public void Send(InnerNetObject netObject, TData data, bool immediately = false) + { + UnsafeSend(netObject, data, immediately); + } + + public void SendTo(InnerNetObject netObject, int targetId, TData data) + { + UnsafeSend(netObject, data, true, targetId); + } + } + + public abstract class PlayerCustomRpc : CustomRpc where TPlugin : BasePlugin + { + protected PlayerCustomRpc(TPlugin plugin) : base(plugin) + { + } + + public void Send(TData data, bool immediately = false) + { + Send(PlayerControl.LocalPlayer, data, immediately); + } + + public void SendTo(int targetId, TData data) + { + SendTo(PlayerControl.LocalPlayer, targetId, data); + } } public class CustomRpcManager @@ -114,72 +191,16 @@ namespace Reactor public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc) { + customRpc.Manager = this; customRpc.Id = List.Count(x => x.UnsafePlugin == customRpc.UnsafePlugin); _list.Add(customRpc); _map[customRpc.InnerNetObjectType].Add(customRpc); + typeof(Rpc<>).MakeGenericType(customRpc.GetType()).GetProperty("Instance")!.SetValue(null, customRpc); + return customRpc; } - public void Send(InnerNetObject netObject, object data, bool immediately = false) - { - UnsafeSend(netObject, List.Single(x => x.GetType() == typeof(TCustomRpc)), data, immediately); - } - - public void SendTo(InnerNetObject netObject, int targetId, object data) - { - UnsafeSend(netObject, List.Single(x => x.GetType() == typeof(TCustomRpc)), data, true, targetId); - } - - public void UnsafeSend(InnerNetObject netObject, UnsafeCustomRpc customRpc, object data, bool immediately = false, int targetClientId = -1) - { - if (netObject == null) throw new ArgumentNullException(nameof(netObject)); - if (customRpc == null) throw new ArgumentNullException(nameof(customRpc)); - if (data == null) throw new ArgumentNullException(nameof(data)); - - if (customRpc.Id == null) - { - throw new InvalidOperationException("Can't send unregistered CustomRpc"); - } - - if (customRpc.LocalHandling == RpcLocalHandling.Before) - { - customRpc.UnsafeHandle(netObject, data); - } - - var writer = immediately switch - { - false => AmongUsClient.Instance.StartRpc(netObject.NetId, CallId, SendOption.Reliable), - true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CallId, SendOption.Reliable, targetClientId) - }; - - if (PluginIdMap.TryGetValue(customRpc.PluginId, out var pluginId)) - { - writer.WritePacked(pluginId); - } - else - { - writer.Write(customRpc.PluginId); - } - - writer.WritePacked(customRpc.Id!.Value); - customRpc.UnsafeWrite(writer, data); - - if (immediately) - { - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - else - { - writer.EndMessage(); - } - - if (customRpc.LocalHandling == RpcLocalHandling.After) - { - customRpc.UnsafeHandle(netObject, data); - } - } - [HarmonyPatch] private static class HandleRpcPatch { @@ -225,4 +246,32 @@ namespace Reactor } } } + + public static class Rpc where T : UnsafeCustomRpc + { + private static T _instance; + + public static T Instance + { + get + { + if (_instance == null) + { + throw new Exception($"{typeof(T).FullName} isn't registered"); + } + + return _instance; + } + + internal set + { + if (_instance != null) + { + throw new Exception($"{typeof(T).FullName} is already registered"); + } + + _instance = value; + } + } + } } diff --git a/Reactor/Extensions/Extensions.cs b/Reactor/Extensions/Extensions.cs index 62a7c66..1ac340c 100644 --- a/Reactor/Extensions/Extensions.cs +++ b/Reactor/Extensions/Extensions.cs @@ -122,14 +122,16 @@ namespace Reactor.Extensions return type.GetMethods(AccessTools.all).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes)); } - public static void Send(this InnerNetObject netObject, object data, bool immediately = false) + [Obsolete("Use Rpc.Instance.Send", true)] + public static void Send(this InnerNetObject netObject, object data, bool immediately = false) where TCustomRpc : UnsafeCustomRpc { - PluginSingleton.Instance.CustomRpcManager.Send(netObject, data, immediately); + Rpc.Instance.UnsafeSend(netObject, data, immediately); } - public static void SendTo(this InnerNetObject netObject, int targetId, object data) + [Obsolete("Use Rpc.Instance.SendTo", true)] + public static void SendTo(this InnerNetObject netObject, int targetId, object data) where TCustomRpc : UnsafeCustomRpc { - PluginSingleton.Instance.CustomRpcManager.SendTo(netObject, targetId, data); + Rpc.Instance.UnsafeSend(netObject, data, true, targetId); } } } diff --git a/Reactor/Extensions/GUIExtensions.cs b/Reactor/Extensions/GUIExtensions.cs index cdc2340..ac97f5a 100644 --- a/Reactor/Extensions/GUIExtensions.cs +++ b/Reactor/Extensions/GUIExtensions.cs @@ -116,6 +116,11 @@ namespace Reactor.Extensions { Rect = Rect.ResetSize(); Rect = GUILayout.Window(Id, Rect, Func, Title, new Il2CppReferenceArray(0)); + + if (Rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y))) + { + Input.ResetInputAxes(); + } } } } diff --git a/Reactor/Extensions/PlayerControlExtensions.cs b/Reactor/Extensions/PlayerControlExtensions.cs new file mode 100644 index 0000000..9b4a5c7 --- /dev/null +++ b/Reactor/Extensions/PlayerControlExtensions.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Reactor.Extensions +{ + public static class PlayerControlExtensions + { + public static Color32 ToPlayerColor(this PlayerColor color) + { + return Palette.PlayerColors[(byte) color]; + } + + public static Color32 ToShadowColor(this PlayerColor color) + { + return Palette.ShadowColors[(byte) color]; + } + } +} diff --git a/Reactor/IsExternalInit.cs b/Reactor/IsExternalInit.cs new file mode 100644 index 0000000..a9deb2a --- /dev/null +++ b/Reactor/IsExternalInit.cs @@ -0,0 +1,11 @@ +// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined?noredirect=1&lq=1 +// IsExternalInit is not included with netstandard2.1 even tho it could + +// ReSharper disable CheckNamespace + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} diff --git a/Reactor/PlayerColor.cs b/Reactor/PlayerColor.cs new file mode 100644 index 0000000..240e400 --- /dev/null +++ b/Reactor/PlayerColor.cs @@ -0,0 +1,18 @@ +namespace Reactor +{ + public enum PlayerColor : byte + { + Red, + Blue, + Green, + Pink, + Orange, + Yellow, + Black, + White, + Purple, + Brown, + Cyan, + Lime + } +} diff --git a/Reactor/PluginSingleton.cs b/Reactor/PluginSingleton.cs index ed0a702..4741877 100644 --- a/Reactor/PluginSingleton.cs +++ b/Reactor/PluginSingleton.cs @@ -4,7 +4,7 @@ using BepInEx.IL2CPP; namespace Reactor { - public class PluginSingleton where T : BasePlugin + public static class PluginSingleton where T : BasePlugin { private static T _instance; diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index dc9e626..5c47099 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -24,6 +24,7 @@ namespace Reactor public ConfigEntry ModdedHandshake; + private GameObject _gameObject; private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher(); public override void Load() @@ -32,9 +33,9 @@ namespace Reactor ModdedHandshake = Config.Bind("Features", "Modded handshake", true); - var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); - gameObject.AddComponent().Plugin = this; - gameObject.AddComponent(); + _gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); + _gameObject.AddComponent().Plugin = this; + _gameObject.AddComponent(); Harmony.PatchAll(); ReactorVersionShower.Initialize(); @@ -43,6 +44,8 @@ namespace Reactor public override bool Unload() { + Harmony.UnpatchSelf(); + _gameObject.Destroy(); RegionInfoWatcher?.Dispose(); return base.Unload(); diff --git a/Reactor/RegisterInIl2CppAttribute.cs b/Reactor/RegisterInIl2CppAttribute.cs index 87ec55c..3b046a9 100644 --- a/Reactor/RegisterInIl2CppAttribute.cs +++ b/Reactor/RegisterInIl2CppAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using HarmonyLib; using UnhollowerRuntimeLib; @@ -16,24 +17,64 @@ namespace Reactor Register(Assembly.GetCallingAssembly()); } + private static readonly AccessTools.FieldRef> _injectedTypes + = AccessTools.FieldRefAccess>(typeof(ClassInjector), "InjectedTypes"); + + private static readonly Func _readClassPointerForType = AccessTools.MethodDelegate>( + AccessTools.Method(typeof(ClassInjector), "ReadClassPointerForType") + ); + + private static bool IsInjected(Type type) + { + if (_readClassPointerForType(type) != IntPtr.Zero) + { + return true; + } + + var injectedTypes = _injectedTypes(); + + lock (injectedTypes) + { + if (injectedTypes.Contains(type.FullName)) + { + return true; + } + } + + return false; + } + + private static void Register(Type type) + { + if (type.BaseType?.GetCustomAttribute() != null) + { + Register(type.BaseType); + } + + if (IsInjected(type)) + { + return; + } + + try + { + typeof(ClassInjector).GetMethod(nameof(ClassInjector.RegisterTypeInIl2Cpp))! + .MakeGenericMethod(type) + .Invoke(null, new object[0]); + } + catch (Exception e) + { + PluginSingleton.Instance.Log.LogWarning($"Failed to register {type.FullDescription()}: {e}"); + } + } + public static void Register(Assembly assembly) { foreach (var type in assembly.GetTypes()) { - var attribute = type.GetCustomAttribute(); - - if (attribute != null) + if (type.GetCustomAttribute() != null) { - try - { - typeof(ClassInjector).GetMethod(nameof(ClassInjector.RegisterTypeInIl2Cpp))! - .MakeGenericMethod(type) - .Invoke(null, new object[0]); - } - catch (Exception e) - { - PluginSingleton.Instance.Log.LogWarning($"Failed to register {type.FullDescription()}: {e}"); - } + Register(type); } } }