private async void HandleToolInteraction(AfterInteractMessage msg)
        {
            if (msg.Handled)
            {
                return;
            }

            // You can only construct/deconstruct things within reach
            if (!msg.CanReach)
            {
                return;
            }

            var targetEnt = msg.Attacked;
            var handEnt   = msg.ItemInHand;

            // A tool has to interact with an entity.
            if (targetEnt is null || handEnt is null)
            {
                return;
            }

            if (!handEnt.InRangeUnobstructed(targetEnt, ignoreInsideBlocker: true))
            {
                return;
            }

            // Cannot deconstruct an entity with no prototype.
            var targetPrototype = targetEnt.MetaData.EntityPrototype;

            if (targetPrototype is null)
            {
                return;
            }

            // the target entity is in the process of being constructed/deconstructed
            if (msg.Attacked.TryGetComponent <ConstructionComponent>(out var constructComp))
            {
                var result = await TryConstructEntity(constructComp, handEnt, msg.User);

                // TryConstructEntity may delete the existing entity

                msg.Handled = result;
            }
            else // try to start the deconstruction process
            {
                // A tool was not used on the entity.
                if (!handEnt.TryGetComponent <IToolComponent>(out var toolComp))
                {
                    return;
                }

                // no known recipe for entity
                if (!_craftRecipes.TryGetValue(targetPrototype.ID, out var prototype))
                {
                    return;
                }

                // there is a recipe, but it can't be deconstructed.
                var lastStep = prototype.Stages[^ 1].Backward;
        /// <summary>
        ///     Checks that the user and target of a
        ///     <see cref="AfterInteractMessage"/> are within a  certain distance
        ///     without any entity that matches the collision mask obstructing them.
        ///     If the <paramref name="range"/> is zero or negative,
        ///     this method will only check if nothing obstructs the entity and component.
        /// </summary>
        /// <param name="args">The event args to use.</param>
        /// <param name="range">
        ///     Maximum distance between the two entity and set of map coordinates.
        /// </param>
        /// <param name="collisionMask">The mask to check for collisions.</param>
        /// <param name="predicate">
        ///     A predicate to check whether to ignore an entity or not.
        ///     If it returns true, it will be ignored.
        /// </param>
        /// <param name="ignoreInsideBlocker">
        ///     If true and both the user and target are inside
        ///     the obstruction, ignores the obstruction and considers the interaction
        ///     unobstructed.
        ///     Therefore, setting this to true makes this check more permissive,
        ///     such as allowing an interaction to occur inside something impassable
        ///     (like a wall). The default, false, makes the check more restrictive.
        /// </param>
        /// <param name="popup">
        ///     Whether or not to popup a feedback message on the user entity for
        ///     it to see.
        /// </param>
        /// <returns>
        ///     True if the two points are within a given range without being obstructed.
        /// </returns>
        public bool InRangeUnobstructed(
            AfterInteractMessage args,
            float range = InteractionRange,
            CollisionGroup collisionMask = CollisionGroup.Impassable,
            Ignored?predicate            = null,
            bool ignoreInsideBlocker     = false,
            bool popup = false)
        {
            var user   = args.User;
            var target = args.Attacked;

            predicate ??= e => e == user;

            MapCoordinates otherPosition;

            if (target == null)
            {
                otherPosition = args.ClickLocation.ToMap(EntityManager);
            }
            else
            {
                otherPosition = target.Transform.MapPosition;
                predicate    += e => e == target;
            }

            return(InRangeUnobstructed(user, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup));
        }
 public static bool InRangeUnobstructed(
     this AfterInteractMessage args,
     float range = InteractionRange,
     CollisionGroup collisionMask = CollisionGroup.Impassable,
     Ignored?predicate            = null,
     bool ignoreInsideBlocker     = false,
     bool popup = false)
 {
     return(SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
                                                        ignoreInsideBlocker, popup));
 }
Example #4
0
        private async void HandleAfterInteract(EntityUid uid, SpawnAfterInteractComponent component, AfterInteractMessage args)
        {
            if (string.IsNullOrEmpty(component.Prototype))
            {
                return;
            }
            if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridId(EntityManager), out var grid))
            {
                return;
            }
            if (!grid.TryGetTileRef(args.ClickLocation, out var tileRef))
            {
                return;
            }

            bool IsTileClear()
            {
                return(tileRef.Tile.IsEmpty == false && args.User.InRangeUnobstructed(args.ClickLocation, popup: true));
            }

            if (!IsTileClear())
            {
                return;
            }

            if (component.DoAfterTime > 0 && TryGet <DoAfterSystem>(out var doAfterSystem))
            {
                var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
                {
                    BreakOnUserMove = true,
                    BreakOnStun     = true,
                    PostCheck       = IsTileClear,
                };
                var result = await doAfterSystem.DoAfter(doAfterArgs);

                if (result != DoAfterStatus.Finished)
                {
                    return;
                }
            }

            if (component.Deleted || component.Owner.Deleted)
            {
                return;
            }

            StackComponent?stack = null;

            if (component.RemoveOnInteract && component.Owner.TryGetComponent(out stack) && !stack.Use(1))
            {
                return;
            }

            EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid, SnapGridOffset.Center));

            if (component.RemoveOnInteract && stack == null && !component.Owner.Deleted)
            {
                component.Owner.Delete();
            }

            return;
        }
        private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractMessage eventArgs)
        {
            // standard interaction checks
            if (!ActionBlockerSystem.CanUse(eventArgs.User))
            {
                return;
            }
            if (!eventArgs.CanReach)
            {
                return;
            }

            // behaviour will depends on target type
            if (eventArgs.Attacked != null)
            {
                // replace broken light in fixture?
                if (eventArgs.Attacked.TryGetComponent(out PoweredLightComponent? fixture))
                {
                    component.TryReplaceBulb(fixture, eventArgs.User);
                }
                // add new bulb to light replacer container?
                else if (eventArgs.Attacked.TryGetComponent(out LightBulbComponent? bulb))
                {
                    component.TryInsertBulb(bulb, eventArgs.User, true);
                }
            }
        }