Example #1
0
        protected void InteractionActivate(EntityUid user, EntityUid used)
        {
            if (EntityManager.TryGetComponent <UseDelayComponent?>(used, out var delayComponent))
            {
                if (delayComponent.ActiveDelay)
                {
                    return;
                }

                delayComponent.BeginDelay();
            }

            if (!_actionBlockerSystem.CanInteract(user) || !_actionBlockerSystem.CanUse(user))
            {
                return;
            }

            // all activates should only fire when in range / unobstructed
            if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
            {
                return;
            }

            // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
            // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
            if (!user.IsInSameOrParentContainer(used) && !CanAccessViaStorage(user, used))
            {
                return;
            }

            var activateMsg = new ActivateInWorldEvent(user, used);

            RaiseLocalEvent(used, activateMsg);
            if (activateMsg.Handled)
            {
                _adminLogSystem.Add(LogType.InteractActivate, LogImpact.Low, $"{user} activated {used}");
                return;
            }

            if (!EntityManager.TryGetComponent(used, out IActivate? activateComp))
            {
                return;
            }

            var activateEventArgs = new ActivateEventArgs(user, used);

            activateComp.Activate(activateEventArgs);
            _adminLogSystem.Add(LogType.InteractActivate, LogImpact.Low, $"{user} activated {used}"); // No way to check success.
        }
        /// <summary>
        ///     Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
        ///     does not request verbs from the server.
        /// </summary>
        public virtual Dictionary <VerbType, SortedSet <Verb> > GetLocalVerbs(EntityUid target, EntityUid user, VerbType verbTypes, bool force = false)
        {
            Dictionary <VerbType, SortedSet <Verb> > verbs = new();

            // accessibility checks
            bool canAccess = false;

            if (force || target == user)
            {
                canAccess = true;
            }
            else if (_interactionSystem.InRangeUnobstructed(user, target, ignoreInsideBlocker: true))
            {
                if (user.IsInSameOrParentContainer(target))
                {
                    canAccess = true;
                }
                else
                {
                    // the item might be in a backpack that the user has open
                    canAccess = _interactionSystem.CanAccessViaStorage(user, target);
                }
            }

            // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
            // call ActionBlocker checks, just cache it for the verb request.
            var canInteract = force || _actionBlockerSystem.CanInteract(user);

            EntityUid @using = default;

            if (EntityManager.TryGetComponent(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUse(user)))
            {
                hands.TryGetActiveHeldEntity(out @using);

                // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
                // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
                // their sprite.
                if (@using != default && EntityManager.TryGetComponent <HandVirtualItemComponent?>(@using, out var pull))
                {
                    @using = pull.BlockingEntity;
                }
            }

            if ((verbTypes & VerbType.Interaction) == VerbType.Interaction)
            {
                GetInteractionVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
                RaiseLocalEvent(target, getVerbEvent);
                verbs.Add(VerbType.Interaction, getVerbEvent.Verbs);
            }

            if ((verbTypes & VerbType.Activation) == VerbType.Activation)
            {
                GetActivationVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
                RaiseLocalEvent(target, getVerbEvent);
                verbs.Add(VerbType.Activation, getVerbEvent.Verbs);
            }

            if ((verbTypes & VerbType.Alternative) == VerbType.Alternative)
            {
                GetAlternativeVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
                RaiseLocalEvent(target, getVerbEvent);
                verbs.Add(VerbType.Alternative, getVerbEvent.Verbs);
            }

            if ((verbTypes & VerbType.Other) == VerbType.Other)
            {
                GetOtherVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
                RaiseLocalEvent(target, getVerbEvent);
                verbs.Add(VerbType.Other, getVerbEvent.Verbs);
            }

            return(verbs);
        }
Example #3
0
        public void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default)
        {
            if (!ValidateInteractAndFace(user, coordinates))
            {
                return;
            }

            if (!_actionBlockerSystem.CanAttack(user))
            {
                return;
            }

            if (!wideAttack)
            {
                // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
                if (targetUid != default && !user.IsInSameOrParentContainer(targetUid) && !CanAccessViaStorage(user, targetUid))
                {
                    Logger.WarningS("system.interaction",
                                    $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(targetUid).EntityName} that isn't the parent, child, or in the same container");
                    return;
                }

                // TODO: Replace with body attack range when we get something like arm length or telekinesis or something.
                if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true))
                {
                    return;
                }
            }

            // Verify user has a hand, and find what object they are currently holding in their active hand
            if (EntityManager.TryGetComponent <HandsComponent?>(user, out var hands))
            {
                if (hands.GetActiveHand?.Owner is { Valid : true } item)
                {
                    if (wideAttack)
                    {
                        var ev = new WideAttackEvent(item, user, coordinates);
                        RaiseLocalEvent(item, ev, false);

                        if (ev.Handled)
                        {
                            _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{user} wide attacked with {item} at {coordinates}");
                            return;
                        }
                    }
                    else
                    {
                        var ev = new ClickAttackEvent(item, user, coordinates, targetUid);
                        RaiseLocalEvent(item, ev, false);

                        if (ev.Handled)
                        {
                            if (targetUid != default)
                            {
                                _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
                                                    $"{user} attacked {targetUid} with {item} at {coordinates}");
                            }
                            else
                            {
                                _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
                                                    $"{user} attacked with {item} at {coordinates}");
                            }

                            return;
                        }
                    }
                }
                else if (!wideAttack && targetUid != default && _entityManager.HasComponent <ItemComponent>(targetUid))
                {
                    // We pick up items if our hand is empty, even if we're in combat mode.
                    InteractHand(user, targetUid);
                    return;
                }
            }
Example #4
0
        /// <summary>
        ///     Resolves user interactions with objects.
        /// </summary>
        /// <remarks>
        ///     Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
        /// </remarks>
        /// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
        /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
        /// interaction. Having an item in the active hand also disables alternative interactions.</param>
        public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid target, bool altInteract = false)
        {
            // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
            if (!altInteract && EntityManager.TryGetComponent(user, out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
            {
                DoAttack(user, coordinates, false, target);
                return;
            }

            if (!ValidateInteractAndFace(user, coordinates))
            {
                return;
            }

            if (!_actionBlockerSystem.CanInteract(user))
            {
                return;
            }

            // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
            // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
            if (target != default && !user.IsInSameOrParentContainer(target) && !CanAccessViaStorage(user, target))
            {
                Logger.WarningS("system.interaction",
                                $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(target).EntityName} that isn't the parent, child, or in the same container");
                return;
            }

            // Verify user has a hand, and find what object they are currently holding in their active hand
            if (!EntityManager.TryGetComponent <HandsComponent?>(user, out var hands))
            {
                return;
            }

            var item = hands.GetActiveHand?.Owner;

            // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
            var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);

            if (target == default || !inRangeUnobstructed)
            {
                if (item == null)
                {
                    return;
                }

                if (!await InteractUsingRanged(user, item.Value, target, coordinates, inRangeUnobstructed) &&
                    !inRangeUnobstructed)
                {
                    var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
                    user.PopupMessage(message);
                }

                return;
            }
            else
            {
                // We are close to the nearby object.
                if (altInteract)
                {
                    // Perform alternative interactions, using context menu verbs.
                    AltInteract(user, target);
                }
                else if (item != null && item != target)
                {
                    // We are performing a standard interaction with an item, and the target isn't the same as the item
                    // currently in our hand. We will use the item in our hand on the nearby object via InteractUsing
                    await InteractUsing(user, item.Value, target, coordinates);
                }
                else if (item == null)
                {
                    // Since our hand is empty we will use InteractHand/Activate
                    InteractHand(user, target);
                }
            }
        }
        public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid?target = null)
        {
            // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
            if (!ValidateInteractAndFace(user, coordinates))
            {
                return;
            }

            if (!_actionBlockerSystem.CanAttack(user))
            {
                return;
            }

            if (!wideAttack)
            {
                // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
                if (target != null && !Deleted(target.Value) && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value))
                {
                    Logger.WarningS("system.interaction",
                                    $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container");
                    return;
                }

                // TODO: Replace with body attack range when we get something like arm length or telekinesis or something.
                if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true))
                {
                    return;
                }
            }

            // Verify user has a hand, and find what object they are currently holding in their active hand
            if (TryComp(user, out HandsComponent? hands))
            {
                var item = hands.GetActiveHand?.Owner;

                if (item != null && !Deleted(item.Value))
                {
                    if (wideAttack)
                    {
                        var ev = new WideAttackEvent(item.Value, user, coordinates);
                        RaiseLocalEvent(item.Value, ev, false);

                        if (ev.Handled)
                        {
                            _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{ToPrettyString(user):user} wide attacked with {ToPrettyString(item.Value):used} at {coordinates}");
                            return;
                        }
                    }
                    else
                    {
                        var ev = new ClickAttackEvent(item.Value, user, coordinates, target);
                        RaiseLocalEvent(item.Value, ev, false);

                        if (ev.Handled)
                        {
                            if (target != null)
                            {
                                _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
                                                    $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}");
                            }
                            else
                            {
                                _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium,
                                                    $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}");
                            }

                            return;
                        }
                    }
                }
                else if (!wideAttack && target != null && HasComp <SharedItemComponent>(target.Value))
                {
                    // We pick up items if our hand is empty, even if we're in combat mode.
                    InteractHand(user, target.Value);
                    return;
                }
            }

            // TODO: Make this saner?
            // Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter.
            if (wideAttack)
            {
                var ev = new WideAttackEvent(user, user, coordinates);
                RaiseLocalEvent(user, ev, false);
                if (ev.Handled)
                {
                    _adminLogSystem.Add(LogType.AttackUnarmedWide, $"{ToPrettyString(user):user} wide attacked at {coordinates}");
                }
            }
            else
            {
                var ev = new ClickAttackEvent(user, user, coordinates, target);
                RaiseLocalEvent(user, ev, false);
                if (ev.Handled)
                {
                    if (target != null)
                    {
                        _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium,
                                            $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}");
                    }
                    else
                    {
                        _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium,
                                            $"{ToPrettyString(user):user} attacked at {coordinates}");
                    }
                }
            }
        }
Example #6
0
        /// <summary>
        ///     Resolves user interactions with objects.
        /// </summary>
        /// <remarks>
        ///     Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
        /// </remarks>
        /// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
        /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
        /// interaction. Having an item in the active hand also disables alternative interactions.</param>
        public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid?target, bool altInteract = false)
        {
            if (target != null && Deleted(target.Value))
            {
                return;
            }

            // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
            if (!altInteract && TryComp(user, out SharedCombatModeComponent? combatMode) && combatMode.IsInCombatMode)
            {
                DoAttack(user, coordinates, false, target);
                return;
            }

            if (!ValidateInteractAndFace(user, coordinates))
            {
                return;
            }

            if (!_actionBlockerSystem.CanInteract(user))
            {
                return;
            }

            // Check if interacted entity is in the same container, the direct child, or direct parent of the user.
            // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
            if (target != null && !user.IsInSameOrParentContainer(target.Value) && !CanAccessViaStorage(user, target.Value))
            {
                return;
            }

            // Verify user has a hand, and find what object they are currently holding in their active hand
            if (!TryComp(user, out SharedHandsComponent? hands))
            {
                return;
            }

            // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
            var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);

            if (target == null || !inRangeUnobstructed)
            {
                if (!hands.TryGetActiveHeldEntity(out var heldEntity))
                {
                    return;
                }

                if (!await InteractUsingRanged(user, heldEntity.Value, target, coordinates, inRangeUnobstructed) &&
                    !inRangeUnobstructed)
                {
                    var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
                    user.PopupMessage(message);
                }

                return;
            }

            // We are close to the nearby object.
            if (altInteract)
            {
                // Perform alternative interactions, using context menu verbs.
                AltInteract(user, target.Value);
            }
            else if (!hands.TryGetActiveHeldEntity(out var heldEntity))
            {
                // Since our hand is empty we will use InteractHand/Activate
                InteractHand(user, target.Value);
            }
            else if (heldEntity != target)
            {
                await InteractUsing(user, heldEntity.Value, target.Value, coordinates);
            }
        }