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); }
/// <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); }
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); }
/// <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); }
/// <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); }
/// <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); }
//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(); }
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); }
/// <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)); }
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(); } }
/// <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); }
//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); }
//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(); }