コード例 #1
0
    private bool CheckAimApply(MouseButtonState buttonState)
    {
        ChangeDirection();
        //currently there is nothing for ghosts to interact with, they only can change facing
        if (PlayerManager.LocalPlayerScript.IsGhost)
        {
            return(false);
        }

        //can't do anything if we have no item in hand
        var handObj = UIManager.Hands.CurrentSlot.Item;

        if (handObj == null)
        {
            triggeredAimApply = null;
            secondsSinceLastAimApplyTrigger = 0;
            return(false);
        }

        var aimApplyInfo = AimApply.ByLocalPlayer(buttonState);

        if (buttonState == MouseButtonState.PRESS)
        {
            //it's being clicked down
            triggeredAimApply = null;
            //Checks for aim apply interactions which can trigger
            var comps = handObj.GetComponents <IBaseInteractable <AimApply> >()
                        .Where(mb => mb != null && (mb as MonoBehaviour).enabled);
            var triggered = InteractionUtils.ClientCheckAndTrigger(comps, aimApplyInfo);
            if (triggered != null)
            {
                triggeredAimApply = triggered;
                secondsSinceLastAimApplyTrigger = 0;
                return(true);
            }
        }
        else
        {
            //it's being held
            //if we are already triggering an AimApply, keep triggering it based on the AimApplyInterval
            if (triggeredAimApply != null)
            {
                secondsSinceLastAimApplyTrigger += Time.deltaTime;
                if (secondsSinceLastAimApplyTrigger > AimApplyInterval)
                {
                    if (triggeredAimApply.CheckInteract(aimApplyInfo, NetworkSide.Client))
                    {
                        //only reset timer if it was actually triggered
                        secondsSinceLastAimApplyTrigger = 0;
                        InteractionUtils.RequestInteract(aimApplyInfo, triggeredAimApply);
                    }
                }

                //no matter what the result, we keep trying to trigger it until mouse is released.
                return(true);
            }
        }

        return(false);
    }
コード例 #2
0
ファイル: InteractionUtils.cs プロジェクト: ewy0/unitystation
 /// <summary>
 /// Use this on client side to request an interaction. This can be used to trigger
 /// interactions manually outside of the normal interaction logic.
 /// Request the server to perform the indicated interaction using the specified
 /// component (or let the server determine the component if interactableComponent is null). The server will still validate the interaction and only perform
 /// it if it validates.
 ///
 /// Note that if interactableComponent implements IClientSideInteractable for this interaction type,
 /// nothing will be done.
 /// </summary>
 /// <param name="interaction">interaction to perform</param>
 /// <param name="interactableComponent">component to handle the interaction, null
 /// if the server should determine which component will trigger from the interaction</param>
 /// <typeparam name="T">type of interaction</typeparam>
 public static void RequestInteract <T>(T interaction, IBaseInteractable <T> interactableComponent = null)
     where T : Interaction
 {
     if (!Cooldowns.TryStartClient(interaction, CommonCooldowns.Instance.Interaction))
     {
         return;
     }
     RequestInteractMessage.Send(interaction, interactableComponent);
 }
コード例 #3
0
    private static bool CheckInteractInternal <T>(IBaseInteractable <T> interactable, T interaction,
                                                  NetworkSide side)
        where T : Interaction
    {
        if (Cooldowns.IsOn(interaction, CooldownID.Asset(CommonCooldowns.Instance.Interaction, side)))
        {
            return(false);
        }
        var result = false;

        //check if client side interaction should be triggered
        if (side == NetworkSide.Client && interactable is IClientInteractable <T> clientInteractable)
        {
            result = clientInteractable.Interact(interaction);
            if (result)
            {
                Logger.LogTraceFormat("ClientInteractable triggered from {0} on {1} for object {2}",
                                      Category.Interaction, typeof(T).Name, clientInteractable.GetType().Name,
                                      (clientInteractable as Component).gameObject.name);
                Cooldowns.TryStartClient(interaction, CommonCooldowns.Instance.Interaction);
                return(true);
            }
        }

        //check other kinds of interactions
        if (interactable is ICheckable <T> checkable)
        {
            result = checkable.WillInteract(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction,
                                      typeof(T).Name, checkable.GetType().Name,
                                      (checkable as Component).gameObject.name);
                return(true);
            }
        }
        else if (interactable is IInteractable <T> )
        {
            //use default logic
            result = DefaultWillInteract.Default(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction,
                                      typeof(T).Name, interactable.GetType().Name,
                                      (interactable as Component).gameObject.name);

                return(true);
            }
        }

        Logger.LogTraceFormat("No interaction triggered from {0} on {1} for object {2}", Category.Interaction,
                              typeof(T).Name, interactable.GetType().Name,
                              (interactable as Component).gameObject.name);

        return(false);
    }
コード例 #4
0
    /// <summary>
    /// Checks if this component would trigger and sends the interaction request to the server if it does. Doesn't
    /// send a message if it only triggered an IClientInteractable (client side only) interaction.
    /// </summary>
    /// <param name="interactable"></param>
    /// <param name="interaction"></param>
    /// <param name="side"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns>true if an interaction was triggered (even if it was clientside-only)</returns>
    public static bool ClientCheckAndRequestInteract <T>(this IBaseInteractable <T> interactable, T interaction) where T : Interaction
    {
        if (CheckInteractInternal(interactable, interaction, NetworkSide.Client, out var wasClientInteractable))
        {
            //if it was a client-side only interaction, we don't send an interaction request
            if (!wasClientInteractable)
            {
                RequestInteract(interaction, interactable);
            }
        }

        return(false);
    }
コード例 #5
0
ファイル: InteractionUtils.cs プロジェクト: ewy0/unitystation
    /// <summary>
    /// Checks if this component would trigger and sends the interaction request to the server if it does. Doesn't
    /// send a message if it only triggered an IClientInteractable (client side only) interaction.
    /// </summary>
    /// <param name="interactable">component to check / trigger</param>
    /// <param name="interaction">interaction attempting to be performed</param>
    /// <typeparam name="T">interaction type</typeparam>
    /// <returns>true if an interaction was triggered (even if it was clientside-only)</returns>
    public static bool ClientCheckAndTrigger <T>(this IBaseInteractable <T> interactable, T interaction) where T : Interaction
    {
        if (CheckInteractInternal(interactable, interaction, NetworkSide.Client, out var wasClientInteractable))
        {
            //if it was a client-side only interaction, we don't send an interaction request
            if (!wasClientInteractable)
            {
                // we defer to the server for deciding what interaction was triggered, unless this is
                // an AimApply in which case there's no reason for such a thing (no shooting-like interactions
                // should have multiple interactable AimApply components)
                RequestInteract(interaction, typeof(T) == typeof(AimApply) ?  interactable : null);
            }

            return(true);
        }

        return(false);
    }
コード例 #6
0
    /// <summary>
    /// Checks if this component would trigger any interaction, also invokes client side logic if it implements IClientInteractable.
    /// </summary>
    /// <param name="interactable"></param>
    /// <param name="interaction"></param>
    /// <param name="side"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static bool CheckInteract <T>(this IBaseInteractable <T> interactable, T interaction, NetworkSide side)
        where T : Interaction
    {
        var result = false;

        //check if client side interaction should be triggered
        if (side == NetworkSide.Client && interactable is IClientInteractable <T> clientInteractable)
        {
            result = clientInteractable.Interact(interaction);
            if (result)
            {
                Logger.LogTraceFormat("ClientInteractable triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, clientInteractable.GetType().Name,
                                      (clientInteractable as Component).gameObject.name);
                return(true);
            }
        }
        else if (interactable is ICheckable <T> checkable)
        {
            result = checkable.WillInteract(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, checkable.GetType().Name,
                                      (checkable as Component).gameObject.name);
                return(true);
            }
        }
        else if (interactable is IInteractable <T> )
        {
            //use default logic
            result = DefaultWillInteract.Default(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, interactable.GetType().Name,
                                      (interactable as Component).gameObject.name);
                return(true);
            }
        }

        Logger.LogTraceFormat("No interaction triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, interactable.GetType().Name,
                              (interactable as Component).gameObject.name);

        return(false);
    }
コード例 #7
0
    //only intended to be used by core if2 classes, please use InteractionUtils.RequestInteract instead.
    public static void Send <T>(T interaction, IBaseInteractable <T> interactableComponent)
        where T : Interaction
    {
        if (typeof(T) == typeof(TileApply))
        {
            Logger.LogError("Cannot use Send with TileApply, please use SendTileApply instead.", Category.Interaction);
            return;
        }
        //never send anything for client-side-only interactions
        if (interactableComponent is IClientInteractable <T> && !(interactableComponent is IInteractable <T>))
        {
            Logger.LogWarningFormat("Interaction request {0} will not be sent because interactable component {1} is" +
                                    " IClientInteractable only (client-side only).", Category.Interaction, interaction, interactableComponent);
            return;
        }
        //if we are client and the interaction has client prediction, trigger it.
        //Note that client prediction is not triggered for server player.
        if (!CustomNetworkManager.IsServer && interactableComponent is IPredictedInteractable <T> predictedInteractable)
        {
            Logger.LogTraceFormat("Predicting {0} interaction for {1} on {2}", Category.Interaction, typeof(T).Name, interactableComponent.GetType().Name, ((Component)interactableComponent).gameObject.name);
            predictedInteractable.ClientPredictInteraction(interaction);
        }
        if (!interaction.Performer.Equals(PlayerManager.LocalPlayer))
        {
            Logger.LogError("Client attempting to perform an interaction on behalf of another player." +
                            " This is not allowed. Client can only perform an interaction as themselves. Message" +
                            " will not be sent.", Category.NetMessage);
            return;
        }

        if (!(interactableComponent is Component))
        {
            Logger.LogError("interactableComponent must be a component, but isn't. The message will not be sent.",
                            Category.NetMessage);
            return;
        }

        var comp = interactableComponent as Component;
        var msg  = new RequestInteractMessage()
        {
            ComponentType   = interactableComponent.GetType(),
            InteractionType = typeof(T),
            ProcessorObject = comp.GetComponent <NetworkIdentity>().netId,
            Intent          = interaction.Intent
        };

        if (typeof(T) == typeof(PositionalHandApply))
        {
            var casted = interaction as PositionalHandApply;
            msg.TargetObject   = casted.TargetObject.NetId();
            msg.TargetVector   = casted.TargetVector;
            msg.TargetBodyPart = casted.TargetBodyPart;
        }
        else if (typeof(T) == typeof(HandApply))
        {
            var casted = interaction as HandApply;
            msg.TargetObject   = casted.TargetObject.NetId();
            msg.TargetBodyPart = casted.TargetBodyPart;
            msg.IsAltUsed      = casted.IsAltClick;
        }
        else if (typeof(T) == typeof(AimApply))
        {
            var casted = interaction as AimApply;
            msg.TargetVector     = casted.TargetVector;
            msg.MouseButtonState = casted.MouseButtonState;
        }
        else if (typeof(T) == typeof(MouseDrop))
        {
            var casted = interaction as MouseDrop;
            msg.TargetObject = casted.TargetObject.NetId();
            msg.UsedObject   = casted.UsedObject.NetId();
        }
        else if (typeof(T) == typeof(InventoryApply))
        {
            var casted = interaction as InventoryApply;
            msg.Storage    = casted.TargetSlot.ItemStorageNetID;
            msg.SlotIndex  = casted.TargetSlot.SlotIdentifier.SlotIndex;
            msg.NamedSlot  = casted.TargetSlot.SlotIdentifier.NamedSlot.GetValueOrDefault(NamedSlot.none);
            msg.UsedObject = casted.UsedObject.NetId();
            msg.IsAltUsed  = casted.IsAltClick;
        }
        msg.Send();
    }
コード例 #8
0
ファイル: InteractionUtils.cs プロジェクト: ewy0/unitystation
    private static bool CheckInteractInternal <T>(this IBaseInteractable <T> interactable, T interaction,
                                                  NetworkSide side, out bool wasClientInteractable)
        where T : Interaction
    {
        wasClientInteractable = false;
        //interactions targeting an object at hiddenpos are NEVER allowed (except for inventory actions,
        //since they can target an object in inventory which means its at hiddenpos)
        if (!(interaction is InventoryApply) && interaction is TargetedInteraction targetedInteraction)
        {
            if (targetedInteraction.TargetObject != null &&
                targetedInteraction.TargetObject.IsAtHiddenPos())
            {
                Logger.LogTraceFormat("Aborting {0} interaction on object {1} because the object is hidden.",
                                      Category.Interaction, typeof(T).Name, targetedInteraction.TargetObject.name);
                return(false);
            }
        }
        if (Cooldowns.IsOn(interaction, CooldownID.Asset(CommonCooldowns.Instance.Interaction, side)))
        {
            return(false);
        }
        var result = false;

        //check if client side interaction should be triggered
        if (side == NetworkSide.Client && interactable is IClientInteractable <T> clientInteractable)
        {
            result = clientInteractable.Interact(interaction);
            if (result)
            {
                Logger.LogTraceFormat("ClientInteractable triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, clientInteractable.GetType().Name,
                                      (clientInteractable as Component).gameObject.name);
                Cooldowns.TryStartClient(interaction, CommonCooldowns.Instance.Interaction);
                wasClientInteractable = true;
                return(true);
            }
        }
        //check other kinds of interactions
        if (interactable is ICheckable <T> checkable)
        {
            result = checkable.WillInteract(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, checkable.GetType().Name,
                                      (checkable as Component).gameObject.name);
                wasClientInteractable = false;
                return(true);
            }
        }
        else if (interactable is IInteractable <T> )
        {
            //use default logic
            result = DefaultWillInteract.Default(interaction, side);
            if (result)
            {
                Logger.LogTraceFormat("WillInteract triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, interactable.GetType().Name,
                                      (interactable as Component).gameObject.name);
                wasClientInteractable = false;
                return(true);
            }
        }

        Logger.LogTraceFormat("No interaction triggered from {0} on {1} for object {2}", Category.Interaction, typeof(T).Name, interactable.GetType().Name,
                              (interactable as Component).gameObject.name);

        wasClientInteractable = false;
        return(false);
    }
コード例 #9
0
ファイル: InteractionUtils.cs プロジェクト: ewy0/unitystation
 /// <summary>
 /// Checks if this component should trigger based on server-side logic.
 /// </summary>
 /// <param name="interactable">component to check</param>
 /// <param name="interaction">interaction attempting to be performed</param>
 /// <typeparam name="T">type of interaction</typeparam>
 /// <returns>true if an interaction would be triggered.</returns>
 public static bool ServerCheckInteract <T>(this IBaseInteractable <T> interactable, T interaction)
     where T : Interaction
 {
     return(CheckInteractInternal(interactable, interaction, NetworkSide.Server, out var unused));
 }
コード例 #10
0
    private void CheckMouseInput()
    {
        if (EventSystem.current.IsPointerOverGameObject())
        {
            //don't do any game world interactions if we are over the UI
            return;
        }

        if (UIManager.IsMouseInteractionDisabled)
        {
            //still allow tooltips
            CheckHover();
            return;
        }

        //do we have a loaded gun
        var loadedGun = GetLoadedGunInActiveHand();

        if (CommonInput.GetMouseButtonDown(0))
        {
            //check ctrl+click for dragging
            if (KeyboardInputManager.IsControlPressed())
            {
                //even if we didn't drag anything, nothing else should happen
                CheckInitiatePull();

                return;
            }

            //check the alt click and throw, which doesn't have any special logic
            if (CheckAltClick())
            {
                return;
            }
            if (CheckThrow())
            {
                return;
            }

            if (loadedGun != null)
            {
                //if we are on harm intent with loaded gun,
                //don't do anything else, just shoot (trigger the AimApply).
                if (UIManager.CurrentIntent == Intent.Harm)
                {
                    CheckAimApply(MouseButtonState.PRESS);
                }
                else
                {
                    //proceed to normal click interaction
                    CheckClickInteractions(true);
                }
            }
            else
            {
                //we don't have a loaded gun
                //Are we over something draggable?
                var draggable = GetDraggable();
                if (draggable != null)
                {
                    //We are over a draggable. We need to wait to see if the user
                    //tries to drag the object or lifts the mouse.
                    potentialDraggable = draggable;
                    dragStartOffset    = MouseWorldPosition - potentialDraggable.transform.position;
                    clickDuration      = 0;
                }
                else
                {
                    //no possibility of dragging something, proceed to normal click logic
                    CheckClickInteractions(true);
                }
            }
        }
        else if (CommonInput.GetMouseButton(0))
        {
            //mouse button being held down.
            //increment the time since they initially clicked the mouse
            clickDuration += Time.deltaTime;

            //If we are possibly dragging and have exceeded the drag distance, initiate the drag
            if (potentialDraggable != null)
            {
                var currentOffset = MouseWorldPosition - potentialDraggable.transform.position;
                if (((Vector2)currentOffset - dragStartOffset).magnitude > MouseDragDeadzone)
                {
                    potentialDraggable.BeginDrag();
                    potentialDraggable = null;
                }
            }

            //continue to trigger the aim apply if it was initially triggered
            CheckAimApply(MouseButtonState.HOLD);
        }
        else if (CommonInput.GetMouseButtonUp(0))
        {
            //mouse button is lifted.
            //If we were waiting for mouseup to trigger a click, trigger it if we're still within
            //the duration threshold
            if (potentialDraggable != null)
            {
                if (clickDuration < MaxClickDuration)
                {
                    //we are lifting the mouse, so AimApply should not be performed but other
                    //clicks can.
                    CheckClickInteractions(false);
                }

                clickDuration      = 0;
                potentialDraggable = null;
            }

            //no more triggering of the current aim apply
            triggeredAimApply = null;
            secondsSinceLastAimApplyTrigger = 0;
        }
        else
        {
            CheckHover();
        }
    }
コード例 #11
0
 /// <summary>
 /// Use this on client side to request an interaction. This can be used to trigger
 /// interactions manually outside of the normal interaction logic.
 /// Request the server to perform the indicated interaction using the specified
 /// component. The server will still validate the interaction and only perform
 /// it if it validates.
 ///
 /// Note that if interactableComponent implements IClientSideInteractable for this interaction type,
 /// nothing will be done.
 /// </summary>
 /// <param name="interaction">interaction to perform</param>
 /// <param name="interactableComponent">component to handle the interaction</param>
 /// <typeparam name="T"></typeparam>
 public static void RequestInteract <T>(T interaction, IBaseInteractable <T> interactableComponent)
     where T : Interaction
 {
     RequestInteractMessage.Send(interaction, interactableComponent);
 }
コード例 #12
0
        //only intended to be used by core if2 classes, please use InteractionUtils.RequestInteract instead.
        //pass null for interactableComponent if you want the server to determine which component of the involved objects should be triggered.
        //(which can be useful when client doesn't have enough info to know which one to trigger)
        public static void Send <T>(T interaction, IBaseInteractable <T> interactableComponent)
            where T : global::Interaction
        {
            if (typeof(T) == typeof(TileApply))
            {
                Logger.LogError("Cannot use Send with TileApply, please use SendTileApply instead.", Category.Interaction);
                return;
            }
            //never send anything for client-side-only interactions
            if (interactableComponent is IClientInteractable <T> && !(interactableComponent is IInteractable <T>))
            {
                Logger.LogWarningFormat("Interaction request {0} will not be sent because interactable component {1} is" +
                                        " IClientInteractable only (client-side only).", Category.Interaction, interaction, interactableComponent);
                return;
            }
            //if we are client and the interaction has client prediction, trigger it.
            //Note that client prediction is not triggered for server player.
            if (!CustomNetworkManager.IsServer && interactableComponent is IPredictedInteractable <T> predictedInteractable)
            {
                Logger.LogTraceFormat("Predicting {0} interaction for {1} on {2}", Category.Interaction, typeof(T).Name, interactableComponent.GetType().Name, ((Component)interactableComponent).gameObject.name);
                predictedInteractable.ClientPredictInteraction(interaction);
            }
            if (!interaction.Performer.Equals(PlayerManager.LocalPlayer))
            {
                Logger.LogError("Client attempting to perform an interaction on behalf of another player." +
                                " This is not allowed. Client can only perform an interaction as themselves. Message" +
                                " will not be sent.", Category.Exploits);
                return;
            }

            if (interactableComponent != null && !(interactableComponent is Component))
            {
                Logger.LogError("interactableComponent must be a component, but isn't. The message will not be sent.",
                                Category.Exploits);
                return;
            }

            var comp = interactableComponent as Component;
            var msg  = new NetMessage()
            {
                ComponentType   = interactableComponent == null ? null : interactableComponent.GetType(),
                InteractionType = typeof(T),
                ProcessorObject = comp == null ? NetId.Invalid : GetNetId(comp.gameObject),
                Intent          = interaction.Intent
            };

            if (typeof(T) == typeof(PositionalHandApply))
            {
                var casted = interaction as PositionalHandApply;
                msg.TargetObject   = GetNetId(casted.TargetObject);
                msg.TargetPosition = casted.TargetPosition;
                msg.TargetBodyPart = casted.TargetBodyPart;
                msg.IsAltUsed      = casted.IsAltClick;
            }
            else if (typeof(T) == typeof(HandApply))
            {
                var casted = interaction as HandApply;
                msg.TargetObject   = GetNetId(casted.TargetObject);
                msg.TargetBodyPart = casted.TargetBodyPart;
                msg.IsAltUsed      = casted.IsAltClick;
            }
            else if (typeof(T) == typeof(AimApply))
            {
                var casted = interaction as AimApply;
                msg.TargetPosition   = casted.TargetPosition;               //TODO add client Origin
                msg.MouseButtonState = casted.MouseButtonState;
                msg.TargetBodyPart   = casted.TargetBodyPart;
            }
            else if (typeof(T) == typeof(MouseDrop))
            {
                var casted = interaction as MouseDrop;
                msg.TargetObject = GetNetId(casted.TargetObject);
                msg.UsedObject   = GetNetId(casted.UsedObject);
            }
            else if (typeof(T) == typeof(InventoryApply))
            {
                var casted = interaction as InventoryApply;

                //StorageIndexOnGameObject
                msg.StorageIndexOnGameObject = 0;
                foreach (var itemStorage in NetworkIdentity.spawned[casted.TargetSlot.ItemStorageNetID].GetComponents <ItemStorage>())
                {
                    if (itemStorage == casted.TargetSlot.ItemStorage)
                    {
                        break;
                    }

                    msg.StorageIndexOnGameObject++;
                }
                msg.Storage    = casted.TargetSlot.ItemStorageNetID;
                msg.SlotIndex  = casted.TargetSlot.SlotIdentifier.SlotIndex;
                msg.NamedSlot  = casted.TargetSlot.SlotIdentifier.NamedSlot.GetValueOrDefault(NamedSlot.none);
                msg.UsedObject = GetNetId(casted.UsedObject);
                msg.IsAltUsed  = casted.IsAltClick;
            }
            else if (typeof(T) == typeof(ConnectionApply))
            {
                var casted = interaction as ConnectionApply;
                msg.TargetObject     = GetNetId(casted.TargetObject);
                msg.TargetPosition   = casted.TargetPosition;
                msg.connectionPointA = casted.WireEndA;
                msg.connectionPointB = casted.WireEndB;
            }
            else if (typeof(T) == typeof(ContextMenuApply))
            {
                var casted = interaction as ContextMenuApply;
                msg.TargetObject    = GetNetId(casted.TargetObject);
                msg.RequestedOption = casted.RequestedOption;
            }
            else if (typeof(T) == typeof(AiActivate))
            {
                var casted = interaction as AiActivate;
                msg.TargetObject = GetNetId(casted.TargetObject);
                msg.ClickTypes   = casted.ClickType;
            }

            Send(msg);
        }
コード例 #13
0
    //only intended to be used by core if2 classes, please use InteractionUtils.RequestInteract instead.
    public static void Send <T>(T interaction, IBaseInteractable <T> interactableComponent)
        where T : Interaction
    {
        //never send anything for client-side-only interactions
        if (interactableComponent is IClientInteractable <T> )
        {
            return;
        }
        //if we are client and the interaction has client prediction, trigger it.
        //Note that client prediction is not triggered for server player.
        if (!CustomNetworkManager.IsServer && interactableComponent is IPredictedInteractable <T> predictedInteractable)
        {
            Logger.LogTraceFormat("Predicting {0} interaction for {1} on {2}", Category.Interaction, typeof(T).Name, interactableComponent.GetType().Name, ((Component)interactableComponent).gameObject.name);
            predictedInteractable.ClientPredictInteraction(interaction);
        }
        if (!interaction.Performer.Equals(PlayerManager.LocalPlayer))
        {
            Logger.LogError("Client attempting to perform an interaction on behalf of another player." +
                            " This is not allowed. Client can only perform an interaction as themselves. Message" +
                            " will not be sent.", Category.NetMessage);
            return;
        }

        if (!(interactableComponent is Component))
        {
            Logger.LogError("interactableComponent must be a component, but isn't. The message will not be sent.",
                            Category.NetMessage);
            return;
        }

        var comp = interactableComponent as Component;
        var msg  = new RequestInteractMessage()
        {
            ComponentType   = interactableComponent.GetType(),
            InteractionType = typeof(T),
            ProcessorObject = comp.GetComponent <NetworkIdentity>().netId
        };

        if (typeof(T) == typeof(PositionalHandApply))
        {
            var casted = interaction as PositionalHandApply;
            msg.TargetObject = casted.TargetObject.GetComponent <NetworkIdentity>().netId;
            msg.TargetVector = casted.TargetVector;
        }
        else if (typeof(T) == typeof(HandApply))
        {
            var casted = interaction as HandApply;
            msg.TargetObject   = casted.TargetObject.GetComponent <NetworkIdentity>().netId;
            msg.TargetBodyPart = casted.TargetBodyPart;
        }
        else if (typeof(T) == typeof(AimApply))
        {
            var casted = interaction as AimApply;
            msg.TargetVector     = casted.TargetVector;
            msg.MouseButtonState = casted.MouseButtonState;
        }
        else if (typeof(T) == typeof(MouseDrop))
        {
            var casted = interaction as MouseDrop;
            msg.TargetObject = casted.TargetObject.GetComponent <NetworkIdentity>().netId;
            msg.UsedObject   = casted.UsedObject.GetComponent <NetworkIdentity>().netId;
        }
        msg.Send();
    }