<|start_filename|>MiniRpcLib/Func/RpcFunc.cs<|end_filename|>
using System;
using System.Linq;
using System.Threading.Tasks;
using RoR2;

namespace MiniRpcLib.Func
{
    internal class RpcFunc : IRpcFunc
    {
        public uint Guid { get; }
        public Type RequestSendType { get; }
        public Type RequestReceiveType { get; }
        
        public Type ResponseSendType { get; }
        public Type ResponseReceiveType { get; }
        
        public int FunctionId { get; }
        public Target ExecuteOn { get; }
        public Func<NetworkUser, object, object> Function { get; }

        public async Task Invoke(object argument, Action<object>[] callbacks, NetworkUser target = null) => 
            await MiniRpc.InvokeFunc(Guid, FunctionId, argument, callbacks, target);

        protected RpcFunc(uint guid, int commandId, Target target, Type requestSendType, Type requestReceiveType, Type responseSendType, Type responseReceiveType, Func<NetworkUser, object, object> func)
        {
            Guid = guid;
            FunctionId = commandId;
            ExecuteOn = target;
            Function = func;
            RequestSendType = requestSendType;
            RequestReceiveType = requestReceiveType;
            
            ResponseSendType = responseSendType;
            ResponseReceiveType = responseReceiveType;
        }
    }

    internal class RpcFunc<TRequestSend, TRequestReceive, TResponseSend, TResponseReceive> : RpcFunc, IRpcFunc<TRequestSend, TResponseReceive>
    {
        public Task InvokeAsync(TRequestSend parameter, Action<TResponseReceive>[] callbacks, NetworkUser target = null) =>
            Invoke(parameter, callbacks.Select(x => new Action<object>(y => x((TResponseReceive)y))).ToArray(), target);

        public void Invoke(TRequestSend parameter, Action<TResponseReceive>[] callbacks, NetworkUser target = null) => 
            InvokeAsync(parameter, callbacks, target);

        public Task InvokeAsync(TRequestSend parameter, Action<TResponseReceive> callback = null, NetworkUser target = null) =>
            InvokeAsync(parameter, new [] { callback }, target);

        public void Invoke(TRequestSend parameter, Action<TResponseReceive> callback = null, NetworkUser target = null) =>
            Invoke(parameter, new[] { callback }, target);

        public RpcFunc(uint guid, int funcId, Target target, Func<NetworkUser, TRequestReceive, TResponseSend> func) :
            base(guid, funcId, target, 
                typeof(TRequestSend), typeof(TRequestReceive), 
                typeof(TResponseSend), typeof(TResponseReceive), 
                (x, y) => func(x, (TRequestReceive)y)) { }
    }
}

<|start_filename|>MiniRpcLib/Action/RpcAction.cs<|end_filename|>
﻿using System;
using RoR2;

namespace MiniRpcLib.Action
{
    internal class RpcAction : IRpcAction
    {
        protected IRpcAction AsIRpcAction => this;
        public uint Guid { get; }
        public Type SendType { get; }
        public Type ReceiveType { get; }
        public int CommandId { get; set; }
        public Target ExecuteOn { get; set; }
        public Action<NetworkUser, object> Action { get; set; }

        public void Invoke(object parameter, NetworkUser target = null)
        {
            MiniRpc.InvokeAction(Guid, CommandId, parameter, target);
        }

        public RpcAction(uint guid, int commandId, Target executeOn, Type sendType, Type receiveType, Action<NetworkUser, object> action)
        {
            Action      = action;
            ExecuteOn   = executeOn;
            SendType    = sendType;
            ReceiveType = receiveType;
            Guid        = guid;
            CommandId   = commandId;
        }
    }

    internal class RpcAction<TSend, TReceive> : RpcAction, IRpcAction<TSend>
    {
        public new Action<NetworkUser, TReceive> Action => (x, y) => AsIRpcAction.Action(x, y);
        public void Invoke(TSend parameter, NetworkUser target = null) => base.Invoke(parameter, target);

        public RpcAction(uint guid, int commandId, Target executeOn, Action<NetworkUser, TReceive> action) :
            base(guid, commandId, executeOn, typeof(TSend), typeof(TReceive), (x, y) => action(x, (TReceive)y)) { }
    }
}

<|start_filename|>MiniRpcLib/MiniRpc.cs<|end_filename|>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MiniRpcLib.Action;
using MiniRpcLib.Extensions;
using MiniRpcLib.Func;
using R2API.Utils;
using RoR2;
using RoR2.Networking;
using UnityEngine.Networking;
using Utilities;
using Random = System.Random;

namespace MiniRpcLib
{
    public static class MiniRpc
    {
        private const string FuncChannelGuid = "mrpc_func";

        private static readonly Dictionary<uint, string> Mods = new Dictionary<uint, string>();

        private static readonly Dictionary<uint, Dictionary<int, IRpcAction>> Actions =
            new Dictionary<uint, Dictionary<int, IRpcAction>>();

        private static readonly Dictionary<uint, Dictionary<int, IRpcFunc>> Functions =
            new Dictionary<uint, Dictionary<int, IRpcFunc>>();

        private static readonly Dictionary<int, Action<NetworkReader>> AwaitingResponse =
            new Dictionary<int, Action<NetworkReader>>();

        private static IRpcAction<Action<NetworkWriter>> _funcRequestS2C;
        private static IRpcAction<Action<NetworkWriter>> _funcResponseS2C;

        private static IRpcAction<Action<NetworkWriter>> _funcRequestC2S;
        private static IRpcAction<Action<NetworkWriter>> _funcResponseC2S;

        private static RpcLayer.RpcLayer _layer;

        private static readonly Random Random = new Random();

        public static void Initialize(RpcLayer.RpcLayer layer)
        {
            _layer = layer;
            _layer.ReceivedC2S += x => HandleRpc(Target.Server, x);
            _layer.ReceivedS2C += x => HandleRpc(Target.Client, x);

            On.RoR2.Networking.NetworkMessageHandlerAttribute.CollectHandlers += orig =>
            {
                orig();
                _layer.Initialize();
            };

            Reflection.InvokeMethod(typeof(NetworkMessageHandlerAttribute), "CollectHandlers");

            var instance = CreateInstance(FuncChannelGuid);

            // Request targets client, Response targets server
            _funcResponseS2C = instance.RegisterAction(Target.Server, HandleFunctionResponse);
            _funcRequestS2C = instance.RegisterAction(Target.Client, (_, x) => HandleFunctionRequest(_funcResponseS2C, null, x));

            // Request targets server, Response targets client
            _funcResponseC2S = instance.RegisterAction(Target.Client, HandleFunctionResponse);
            _funcRequestC2S = instance.RegisterAction(Target.Server, (nu, x) => HandleFunctionRequest(_funcResponseC2S, nu, x));
        }

        private static void HandleFunctionRequest(IRpcAction<Action<NetworkWriter>> response, NetworkUser nu, NetworkReader reader)
        {
            //Logger.Info("HandleFunctionRequest");
            var guid = reader.ReadUInt32();
            var funcId = reader.ReadInt32();
            var invokeId = reader.ReadInt32();
            //Logger.Info($"Received function: {guid}[{funcId}] - {invokeId}");
            var func = Functions[guid][funcId];
            var result = func.Function.Invoke(nu,
                func.RequestReceiveType == typeof(NetworkReader)
                    ? reader
                    : reader.ReadObject(func.RequestReceiveType));

            response.Invoke(writer =>
            {
                writer.Write(invokeId);
                writer.WriteObject(result);
            }, nu);
        }

        private static void HandleFunctionResponse(NetworkUser nu, NetworkReader reader)
        {
            //Logger.Debug("HandleFunctionResponse");
            var invokeId = reader.ReadInt32();
            //Logger.Debug($"Received function return: {invokeId}");
            AwaitingResponse[invokeId].Invoke(reader);
        }

        public static MiniRpcInstance CreateInstance(string guid)
        {
            var hash = Hash.JenkinsOAAT(guid);
            Mods.Add(hash, guid);
            Actions.Add(hash, new Dictionary<int, IRpcAction>());
            Functions.Add(hash, new Dictionary<int, IRpcFunc>());
            return new MiniRpcInstance(hash);
        }

        private static void HandleRpc(Target commandType, NetworkMessage netMsg)
        {
            var hash      = netMsg.reader.ReadUInt32();
            var commandId = netMsg.reader.ReadInt32();

            if (!Actions.TryGetValue(hash, out var actions) || !actions.TryGetValue(commandId, out var action))
            {
                Logger.Error($"{commandType} Received unregistered CommandId: {hash}[{commandId}]");
                return;
            }

            //Logger.Debug($"{Mods[hash]}[{commandId}]");

            if (action.ExecuteOn != commandType)
            {
                Logger.Error($"Can not invoke {commandType} command as {action.ExecuteOn}.");
                return;
            }

            try
            {
                var nu = NetworkUser.readOnlyInstancesList.FirstOrDefault(v => v.connectionToClient == netMsg.conn);

                action.Action.Invoke(nu,
                    action.ReceiveType == typeof(NetworkReader)
                        ? netMsg.reader
                        : netMsg.reader.ReadObject(action.ReceiveType));
            }
            catch (Exception e)
            {
                Logger.Error($"Failed to invoke C2S command: {hash}[{commandId}] | {e}");
                throw;
            }
        }

        internal static IRpcAction<T> RegisterAction<T>(uint guid, Target target, Action<NetworkUser, T> action, int? id = null)
            => RegisterAction<T, T>(guid, target, action, id);

        internal static IRpcAction<TSend> RegisterAction<TSend, TReceive>(uint guid, Target target, Action<NetworkUser, TReceive> action, int? id = null)
        {
            var actions = Actions[guid];
            var intId = id ?? actions.Count;

            //Logger.Info($"{guid}[{intId}] Registering action | {target}");
            var rpcAction = new RpcAction<TSend, TReceive>(guid, actions.Count, target, action);

            actions.Add(intId, rpcAction);
            return rpcAction;
        }

        internal static IRpcFunc<TRequest, TResponse> RegisterFunc<TRequest, TResponse>
            (uint guid, Target target, Func<NetworkUser, TRequest, TResponse> func, int? id = null)
            => RegisterFunc<TRequest, TRequest, TResponse, TResponse>(guid, target, func, id);

        internal static IRpcFunc<TRequestSend, TResponseReceive> RegisterFunc<TRequestSend, TRequestReceive, TResponseSend, TResponseReceive>
            (uint guid, Target target, Func<NetworkUser, TRequestReceive, TResponseSend> func, int? id = null)
        {
            var functions = Functions[guid];
            var intId = id ?? functions.Count;

            //Logger.Info($"{guid}[{intId}] Registering action | {target}");
            var rpcFunc = new RpcFunc<TRequestSend, TRequestReceive, TResponseSend, TResponseReceive>(guid, intId, target, func);

            functions.Add(intId, rpcFunc);
            return rpcFunc;
        }

        internal static async Task InvokeFunc(uint guid, int funcId, object argument, Action<object>[] callbacks, NetworkUser target = null)
        {
            var invokeId = Random.Next(int.MinValue, int.MaxValue);
            var t = new TaskCompletionSource<object>();

            var func = Functions[guid][funcId];
            int targetCount;

            switch (func.ExecuteOn)
            {
                case Target.Server:
                case Target.Client when target != null:
                    targetCount = 1;
                    break;
                case Target.Client:
                    targetCount = NetworkServer.connections.Count;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            AwaitingResponse[invokeId] = x =>
            {
                if (--targetCount <= 0)
                {
                    AwaitingResponse.Remove(invokeId);
                    t.SetResult(null);
                }

                var retReceiveType = Functions[guid][funcId].ResponseReceiveType;
                //Logger.Info($"AwaitingResponse[invokeId] {retReceiveType}");
                var result = typeof(NetworkReader) == retReceiveType ? x : x.ReadObject(retReceiveType);
                foreach (var callback in callbacks)
                    callback(result);
                //Logger.Info($"AwaitingResponse[invokeId] {result}");
            };


            (func.ExecuteOn == Target.Client ? _funcRequestS2C : _funcRequestC2S).Invoke(x =>
            {
                x.Write(guid);
                x.Write(funcId);
                x.Write(invokeId);
                x.WriteObject(argument);
            }, target);

            await t.Task;
        }

        internal static void InvokeAction(uint guid, int commandId, object argument, NetworkUser target = null)
        {
            var rpc = Actions[guid][commandId];

            if (rpc.SendType != argument.GetType())
                throw new ArgumentException($"The passed argument type ({argument.GetType()})is not the correct type ({rpc.SendType}).", nameof(argument));

            switch (rpc.ExecuteOn)
            {
                case Target.Server:
                    if (target && target.connectionToServer != ClientScene.readyConnection)
                        throw new ArgumentException("Specifying a target is not allowed for C2S packets as they are always sent to the server.");

                    _layer.SendC2S(guid, commandId, argument);
                    break;
                case Target.Client:
                    _layer.SendS2C(guid, commandId, argument, target);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}


<|start_filename|>MiniRpcLib/MiniRpcLib.cs<|end_filename|>
﻿using BepInEx;
using MiniRpcLib.RpcLayer;

namespace MiniRpcLib
{
    [BepInDependency("com.bepis.r2api")]
    [BepInPlugin(ModGuid, ModName, ModVer)]
    public class MiniRpcPlugin : BaseUnityPlugin
    {
        public const string Dependency = ModGuid;

        private const string ModVer = "1.1";
        private const string ModName = "MiniRpcLib";
        private const string ModGuid = "dev.wildbook.libminirpc";

        public MiniRpcPlugin()
        {
            Logger.LogInfo("Initializing Logger");
            InitLogger();

            Logger.LogInfo("Initializing MiniRpc");
            MiniRpc.Initialize(new UnityMessageHandler());
        }

        public void InitLogger()
        {
            MiniRpcLib.Logger.Debug   = Logger.LogDebug;
            MiniRpcLib.Logger.Error   = Logger.LogError;
            MiniRpcLib.Logger.Fatal   = Logger.LogFatal;
            MiniRpcLib.Logger.Info    = Logger.LogInfo;
            MiniRpcLib.Logger.Message = Logger.LogMessage;
            MiniRpcLib.Logger.Warning = Logger.LogWarning;
        }
    }
}


<|start_filename|>MiniRpcLib/Action/IRpcAction.cs<|end_filename|>
﻿using System;
using RoR2;

namespace MiniRpcLib.Action
{
    public interface IRpcAction
    {
        uint Guid { get; }
        Type SendType { get; }
        Type ReceiveType { get; }
        int CommandId { get; }
        Target ExecuteOn { get; }
        Action<NetworkUser, object> Action { get; }
        void Invoke(object parameter, NetworkUser target = null);
    }

    public interface IRpcAction<in TSend>
    {
        uint Guid { get; }
        Type SendType { get; }
        Type ReceiveType { get; }
        int CommandId { get; }
        Target ExecuteOn { get; }
        Action<NetworkUser, object> Action { get; }
        void Invoke(TSend parameter, NetworkUser target = null);
    }
}

<|start_filename|>MiniRpcLib/Logger.cs<|end_filename|>
using System;

namespace MiniRpcLib
{
    internal class Logger
    {
        public static Action<object> Fatal   = delegate { };
        public static Action<object> Error   = delegate { };
        public static Action<object> Warning = delegate { };
        public static Action<object> Message = delegate { };
        public static Action<object> Info    = delegate { };
        public static Action<object> Debug   = delegate { };
    }
}

<|start_filename|>MiniRpcLib/JenkinsHash.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Utilities
{
    public static class Hash
    {
        public static uint JenkinsOAAT(string data) => JenkinsOAAT(Encoding.UTF8.GetBytes(data));

        public static uint JenkinsOAAT(byte[] data)
        {
            uint hash = 0;
            foreach (var i in data)
            {
                hash += i;
                hash += hash << 10;
                hash ^= hash >> 6;
            }
            hash += hash << 3;
            hash ^= hash >> 11;
            hash += hash << 15;
            return hash;
        }
    }
}


<|start_filename|>MiniRpcLib/MethodHelper.cs<|end_filename|>
﻿using System;
using System.Reflection;

namespace MiniRpcLib.Extensions
{
    public static class MethodHelper
    {
        public  static MethodInfo GetMethodInfo<T>(this T a) where T : Delegate => a.Method;
    }
}


<|start_filename|>MiniRpcLib/Extensions/NetworkExtensions.cs<|end_filename|>
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using UnityEngine;
using UnityEngine.Networking;

namespace MiniRpcLib.Extensions
{
    internal static class NetworkExtensions
    {
        public static void WriteObject(this NetworkWriter writer, object obj)
        {
            switch (obj)
            {
                case Color x:
                    writer.Write(x);
                    break;
                case Color32 x:
                    writer.Write(x);
                    break;
                case GameObject x:
                    writer.Write(x);
                    break;
                case Matrix4x4 x:
                    writer.Write(x);
                    break;
                case MessageBase x:
                    writer.Write(x);
                    break;
                case NetworkHash128 x:
                    writer.Write(x);
                    break;
                case NetworkIdentity x:
                    writer.Write(x);
                    break;
                case NetworkInstanceId x:
                    writer.Write(x);
                    break;
                case NetworkSceneId x:
                    writer.Write(x);
                    break;
                case Plane x:
                    writer.Write(x);
                    break;
                case Quaternion x:
                    writer.Write(x);
                    break;
                case Ray x:
                    writer.Write(x);
                    break;
                case Rect x:
                    writer.Write(x);
                    break;
                case Transform x:
                    writer.Write(x);
                    break;
                case Vector2 x:
                    writer.Write(x);
                    break;
                case Vector3 x:
                    writer.Write(x);
                    break;
                case Vector4 x:
                    writer.Write(x);
                    break;
                case bool x:
                    writer.Write(x);
                    break;
                case byte x:
                    writer.Write(x);
                    break;
                case byte[] x:
                    writer.WriteBytesFull(x);
                    break;
                case char x:
                    writer.Write(x);
                    break;
                case decimal x:
                    writer.Write(x);
                    break;
                case double x:
                    writer.Write(x);
                    break;
                case float x:
                    writer.Write(x);
                    break;
                case int x:
                    writer.Write(x);
                    break;
                case long x:
                    writer.Write(x);
                    break;
                case sbyte x:
                    writer.Write(x);
                    break;
                case short x:
                    writer.Write(x);
                    break;
                case ushort x:
                    writer.Write(x);
                    break;
                case string x:
                    writer.Write(x);
                    break;
                case uint x:
                    writer.Write(x);
                    break;
                case ulong x:
                    writer.Write(x);
                    break;
                case Action<NetworkWriter> x:
                    x(writer);
                    break;
                default:
                    throw new ArgumentException(
                        $"The argument passed to WriteObject ({obj.GetType()}) is not a type supported by NetworkWriter.",
                        nameof(obj));
            }
        }

        public static object ReadObject(this NetworkReader reader, Type type)
        {
            var @switch = new Dictionary<Type, Func<object>>
            {
                {typeof(Color), () => reader.ReadColor()},
                {typeof(Color32), () => reader.ReadInt32()},
                {typeof(GameObject), reader.ReadGameObject},
                {typeof(Matrix4x4), () => reader.ReadMatrix4x4()},
                {typeof(NetworkHash128), () => reader.ReadNetworkHash128()},
                {typeof(NetworkIdentity), reader.ReadNetworkIdentity},
                {typeof(NetworkInstanceId), () => reader.ReadNetworkId()},
                {typeof(NetworkSceneId), () => reader.ReadSceneId()},
                {typeof(Plane), () => reader.ReadPlane()},
                {typeof(Quaternion), () => reader.ReadQuaternion()},
                {typeof(Ray), () => reader.ReadRay()},
                {typeof(Rect), () => reader.ReadRect()},
                {typeof(Transform), reader.ReadTransform},
                {typeof(Vector2), () => reader.ReadVector2()},
                {typeof(Vector3), () => reader.ReadVector3()},
                {typeof(Vector4), () => reader.ReadVector4()},

                {typeof(bool), () => reader.ReadBoolean()},
                {typeof(byte[]), reader.ReadBytesAndSize},
                {typeof(char), () => reader.ReadChar()},
                {typeof(decimal), () => reader.ReadDecimal()},
                {typeof(double), () => reader.ReadDouble()},
                {typeof(float), () => reader.ReadSingle()},

                {typeof(sbyte), () => reader.ReadSByte()},
                {typeof(string), reader.ReadString},

                {typeof(short), () => reader.ReadInt16()},
                {typeof(int), () => reader.ReadInt32()},
                {typeof(long), () => reader.ReadInt64()},

                {typeof(ushort), () => reader.ReadUInt16()},
                {typeof(uint), () => reader.ReadUInt32()},
                {typeof(ulong), () => reader.ReadUInt64()},
            };

            if (!@switch.ContainsKey(type))
            {
                if (typeof(MessageBase).IsAssignableFrom(type))
                {
                    MessageBase instance;
                    var constructor = type.GetConstructor(Type.EmptyTypes);

                    if (constructor != null)
                    {
                        instance = (MessageBase)constructor.Invoke(null);
                    }
                    else
                    {
                        instance = (MessageBase)FormatterServices.GetUninitializedObject(type);
                    }

                    instance.Deserialize(reader);
                    return instance;
                }

                throw new ArgumentException($"The type ({type}) passed to ReadObject is not a type supported by NetworkReader.", nameof(type));

            }

            return @switch[type]();
        }

    }
}


<|start_filename|>CameraUtilities/Metadata/manifest.json<|end_filename|>
{
  "name": "CameraUtilities",
  "version_number": "1.0.0",
  "website_url": "https://github.com/wildbook/R2Mods/tree/master/CameraUtilities",
  "description": "Ever wanted to change your mouse sensitivity when sprinting?",
  "dependencies": [
    "bbepis-BepInExPack-2.0.0",
    "tristanmcpherson-R2API-2.2.32"
  ]
}

<|start_filename|>MiniRpcLib/Func/IRpcFunc.cs<|end_filename|>
using System;
using System.Threading.Tasks;
using RoR2;

namespace MiniRpcLib.Func
{
    public interface IRpcFunc<in TRequestSend, out TResponseReceive>
    {
        Task InvokeAsync(TRequestSend parameter, Action<TResponseReceive>[] callbacks = null, NetworkUser target = null);
        Task InvokeAsync(TRequestSend parameter, Action<TResponseReceive> callback = null, NetworkUser target = null);

        void Invoke(TRequestSend parameter, Action<TResponseReceive>[] callbacks = null, NetworkUser target = null);
        void Invoke(TRequestSend parameter, Action<TResponseReceive> callback = null, NetworkUser target = null);
    }

    public interface IRpcFunc
    {
        uint Guid { get; }
        Type RequestSendType { get; }
        Type RequestReceiveType { get; }
        
        Type ResponseSendType { get; }
        Type ResponseReceiveType { get; }
        int FunctionId { get; }
        Target ExecuteOn { get; }
        Func<NetworkUser, object, object> Function { get; }
        Task Invoke(object parameter, Action<object>[] callbacks = null, NetworkUser user = null);
    }
}

<|start_filename|>MiniRpcLib/RpcLayer/RpcLayer.cs<|end_filename|>
﻿using RoR2;
using System;
using UnityEngine.Networking;

namespace MiniRpcLib.RpcLayer
{
    public abstract class RpcLayer
    {
        public abstract void Initialize();
        public event Action<NetworkMessage> ReceivedC2S;
        public event Action<NetworkMessage> ReceivedS2C;

        public abstract void SendC2S(uint guid, int commandId, object argument);
        public abstract void SendS2C(uint guid, int commandId, object argument, NetworkUser target = null);

        protected void OnReceivedC2S(NetworkMessage x) => ReceivedC2S?.Invoke(x);
        protected void OnReceivedS2C(NetworkMessage x) => ReceivedS2C?.Invoke(x);
    }
}


<|start_filename|>MiniRpcLib/RpcTarget.cs<|end_filename|>
﻿namespace MiniRpcLib
{
    public enum Target
    {
        Server,
        Client,
    }
}

<|start_filename|>MiniRpcLib/MiniRpcInstance.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using MiniRpcLib.Action;
using MiniRpcLib.Func;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;

namespace MiniRpcLib
{
    public class MiniRpcInstance
    {
        private readonly uint _guid;

        internal MiniRpcInstance(uint guid) => _guid = guid;

        public IRpcAction<Action<NetworkWriter>> RegisterAction(Target target, Action<NetworkUser, NetworkReader> action, int? id = null)
            => MiniRpc.RegisterAction<Action<NetworkWriter>, NetworkReader>(_guid, target, action, id);

        public IRpcFunc<NetworkReader, Action<NetworkWriter>> RegisterFunc(Target target, Func<NetworkUser, NetworkReader, Action<NetworkWriter>> action, int? id = null)
            => MiniRpc.RegisterFunc(_guid, target, action, id);

        public IRpcAction<TArg> RegisterAction<TArg, TIdEnum>(Target target, Action<NetworkUser, TArg> action, TIdEnum id) where TIdEnum : Enum
            => RegisterAction(target, action, (int)(object)id);

        public IRpcFunc<TArg, TReturn> RegisterFunc<TArg, TReturn, TIdEnum>(Target target, Func<NetworkUser, TArg, TReturn> action, TIdEnum id) where TIdEnum : Enum
            => RegisterFunc<TArg, TReturn>(target, action, (int)(object)id);

        public IRpcAction<TArg> RegisterAction<TArg>(Target target, Action<NetworkUser, TArg> action, int? id = null)
        {
            if (!_typesBase.Contains(typeof(TArg)) && !typeof(MessageBase).IsAssignableFrom(typeof(TArg)))
                throw new NotSupportedException($"Type {typeof(TArg)} is not a valid argument type. If this is a type of yours, please implement INetworkSerializable.");

            return MiniRpc.RegisterAction(_guid, target, action, id);
        }

        public IRpcFunc<TArg, TReturn> RegisterFunc<TArg, TReturn>(Target target, Func<NetworkUser, TArg, TReturn> action, int? id = null)
        {
            if (!_typesBase.Contains(typeof(TArg)) && !typeof(MessageBase).IsAssignableFrom(typeof(TArg)))
                throw new NotSupportedException($"Type {typeof(TArg)} is not a valid argument type. If this is a type of yours, please implement INetworkSerializable.");

            if (!_typesBase.Contains(typeof(TReturn)) && !typeof(MessageBase).IsAssignableFrom(typeof(TReturn)))
                throw new NotSupportedException($"Type {typeof(TReturn)} is not a valid return value type. If this is a type of yours, please implement INetworkSerializable.");

            return MiniRpc.RegisterFunc(_guid, target, action, id);
        }

        private readonly HashSet<Type> _typesBase = new HashSet<Type>
        {
            typeof(Color),
            typeof(Color32),
            typeof(GameObject),
            typeof(Matrix4x4),
            typeof(NetworkHash128),
            typeof(NetworkIdentity),
            typeof(NetworkInstanceId),
            typeof(NetworkSceneId),
            typeof(Plane),
            typeof(Quaternion),
            typeof(Ray),
            typeof(Rect),
            typeof(Transform),
            typeof(Vector2),
            typeof(Vector3),
            typeof(Vector4),
            typeof(bool),
            typeof(byte),
            typeof(byte[]),
            typeof(char),
            typeof(decimal),
            typeof(double),
            typeof(float),
            typeof(int),
            typeof(long),
            typeof(sbyte),
            typeof(string),
            typeof(short),
            typeof(ushort),
            typeof(uint),
            typeof(ulong),
        };
    }
}