SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat)
        {
            // check that victim even have head
            if (_entMan.TryGetComponent <SharedBodyComponent?>(victim, out var body) &&
                body.HasPartOfType(BodyPartType.Head))
            {
                var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others", ("victim", Name: _entMan.GetComponent <MetaDataComponent>(victim).EntityName), ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName));
                victim.PopupMessageOtherClients(othersMessage);

                var selfMessage = Loc.GetString("toilet-component-suicide-head-message", ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName));
                victim.PopupMessage(selfMessage);

                return(SuicideKind.Asphyxiation);
            }
            else
            {
                var othersMessage = Loc.GetString("toilet-component-suicide-message-others", ("victim", Name: _entMan.GetComponent <MetaDataComponent>(victim).EntityName), ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName));
                victim.PopupMessageOtherClients(othersMessage);

                var selfMessage = Loc.GetString("toilet-component-suicide-message", ("owner", Name: _entMan.GetComponent <MetaDataComponent>(Owner).EntityName));
                victim.PopupMessage(selfMessage);

                return(SuicideKind.Blunt);
            }
        }
        private void TurnOn(StunbatonComponent comp, EntityUid user)
        {
            if (comp.Activated)
            {
                return;
            }

            if (!EntityManager.TryGetComponent <SpriteComponent?>(comp.Owner, out var sprite) ||
                !EntityManager.TryGetComponent <SharedItemComponent?>(comp.Owner, out var item))
            {
                return;
            }

            var playerFilter = Filter.Pvs(comp.Owner);

            if (!_cellSystem.TryGetBatteryFromSlot(comp.Owner, out var battery))
            {
                SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
                user.PopupMessage(Loc.GetString("comp-stunbaton-activated-missing-cell"));
                return;
            }

            if (battery.CurrentCharge < comp.EnergyPerUse)
            {
                SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));
                user.PopupMessage(Loc.GetString("comp-stunbaton-activated-dead-cell"));
                return;
            }

            SoundSystem.Play(playerFilter, comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f));

            item.EquippedPrefix = "on";
            sprite.LayerSetState(0, "stunbaton_on");
            comp.Activated = true;
        }
        /// <returns>The actual amount transferred.</returns>
        private static FixedPoint2 DoTransfer(EntityUid user,
                                              EntityUid sourceEntity,
                                              Solution source,
                                              EntityUid targetEntity,
                                              Solution target,
                                              FixedPoint2 amount)
        {
            if (source.DrainAvailable == 0)
            {
                sourceEntity.PopupMessage(user,
                                          Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)));
                return(FixedPoint2.Zero);
            }

            if (target.AvailableVolume == 0)
            {
                targetEntity.PopupMessage(user,
                                          Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity)));
                return(FixedPoint2.Zero);
            }

            var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.DrainAvailable, target.AvailableVolume));

            var solution = EntitySystem.Get <SolutionContainerSystem>().Drain(sourceEntity, source, actualAmount);

            EntitySystem.Get <SolutionContainerSystem>().Refill(targetEntity, target, solution);

            return(actualAmount);
        }
        private void TurnOn(StunbatonComponent comp, EntityUid user)
        {
            if (comp.Activated)
            {
                return;
            }

            var playerFilter = Filter.Pvs(comp.Owner, entityManager: EntityManager);

            if (!TryComp <BatteryComponent>(comp.Owner, out var battery) || battery.CurrentCharge < comp.EnergyPerUse)
            {
                SoundSystem.Play(comp.TurnOnFailSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f));
                user.PopupMessage(Loc.GetString("stunbaton-component-low-charge"));
                return;
            }

            if (EntityManager.TryGetComponent <AppearanceComponent>(comp.Owner, out var appearance) &&
                EntityManager.TryGetComponent <ItemComponent>(comp.Owner, out var item))
            {
                _item.SetHeldPrefix(comp.Owner, "on", item);
                appearance.SetData(ToggleVisuals.Toggled, true);
            }

            SoundSystem.Play(comp.SparksSound.GetSound(), playerFilter, comp.Owner, AudioHelpers.WithVariation(0.25f));
            comp.Activated = true;
        }
Esempio n. 5
0
        // ECS this out!, Handleable SuicideEvent?
        SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat)
        {
            var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other", ("victim", victim));

            victim.PopupMessageOtherClients(othersMessage);

            var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self");

            victim.PopupMessage(selfMessage);

            return(SuicideKind.Piercing);
        }
Esempio n. 6
0
        private void OnOpenTaggerUIAttempt(EntityUid uid, DisposalTaggerComponent router, ActivatableUIOpenAttemptEvent args)
        {
            if (!TryComp<HandsComponent>(args.User, out var hands))
            {
                uid.PopupMessage(args.User, Loc.GetString("disposal-tagger-window-activate-no-hands"));
                return;
            }

            var activeHandEntity = hands.ActiveHandEntity;
            if (activeHandEntity != null)
            {
                args.Cancel();
            }
        }
Esempio n. 7
0
        SuicideKind ISuicideAct.Suicide(EntityUid victim, IChatManager chat)
        {
            var headCount = 0;

            if (_entities.TryGetComponent <SharedBodyComponent?>(victim, out var body))
            {
                var headSlots = body.GetSlotsOfType(BodyPartType.Head);

                foreach (var slot in headSlots)
                {
                    var part = slot.Part;

                    if (part == null ||
                        !body.TryDropPart(slot, out var dropped))
                    {
                        continue;
                    }

                    foreach (var droppedPart in dropped.Values)
                    {
                        if (droppedPart.PartType != BodyPartType.Head)
                        {
                            continue;
                        }

                        _storage.Insert(droppedPart.Owner);
                        headCount++;
                    }
                }
            }

            var othersMessage = headCount > 1
                ? Loc.GetString("microwave-component-suicide-multi-head-others-message", ("victim", victim))
                : Loc.GetString("microwave-component-suicide-others-message", ("victim", victim));

            victim.PopupMessageOtherClients(othersMessage);

            var selfMessage = headCount > 1
                ? Loc.GetString("microwave-component-suicide-multi-head-message")
                : Loc.GetString("microwave-component-suicide-message");

            victim.PopupMessage(selfMessage);

            _currentCookTimerTime = 10;
            ClickSound();
            UIDirty = true;
            Wzhzhzh();
            return(SuicideKind.Heat);
        }
Esempio n. 8
0
        /// <summary>
        /// If not handled, does the default suicide, which is biting your own tongue
        /// </summary>
        private static void DefaultSuicideHandler(EntityUid victim, SuicideEvent suicideEvent)
        {
            if (suicideEvent.Handled)
            {
                return;
            }
            var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim));

            victim.PopupMessageOtherClients(othersMessage);

            var selfMessage = Loc.GetString("suicide-command-default-text-self");

            victim.PopupMessage(selfMessage);
            suicideEvent.SetHandled(SuicideKind.Bloodloss);
        }
        /// <summary>
        ///     Pops up a message for every player around <see cref="source"/> to see,
        ///     except for <see cref="source"/> itself.
        /// </summary>
        /// <param name="source">The entity on which to popup the message.</param>
        /// <param name="message">The message to show.</param>
        public static void PopupMessageOtherClients(this EntityUid source, string message)
        {
            var viewers = Filter.Empty()
                          .AddPlayersByPvs(source)
                          .Recipients;

            foreach (var viewer in viewers)
            {
                if (viewer.AttachedEntity is not {
                    Valid: true
                } viewerEntity || source == viewerEntity || viewer.AttachedEntity == null)
                {
                    continue;
                }

                source.PopupMessage(viewerEntity, message);
            }
        }
        public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
        {
            if (!_blocker.CanInteract(entity) ||
                !EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
                component.SubscribedPilots.Contains(pilotComponent))
            {
                return;
            }

            component.SubscribedPilots.Add(pilotComponent);

            if (EntityManager.TryGetComponent(entity, out ServerAlertsComponent? alertsComponent))
            {
                alertsComponent.ShowAlert(AlertType.PilotingShuttle);
            }

            entity.PopupMessage(Loc.GetString("shuttle-pilot-start"));
            pilotComponent.Console  = component;
            pilotComponent.Position = EntityManager.GetComponent <TransformComponent>(entity).Coordinates;
            pilotComponent.Dirty();
        }
Esempio n. 11
0
        public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet = false)
        {
            // Do they have enough hands free?
            if (!EntityManager.TryGetComponent <HandsComponent>(user, out var hands))
            {
                if (!quiet)
                {
                    user.PopupMessage(Loc.GetString("wieldable-component-no-hands"));
                }
                return(false);
            }

            if (hands.CountFreeHands()
                < component.FreeHandsRequired)
            {
                // TODO FLUENT need function to change 'hands' to 'hand' when there's only 1 required
                if (!quiet)
                {
                    user.PopupMessage(Loc.GetString("wieldable-component-not-enough-free-hands",
                                                    ("number", component.FreeHandsRequired),
                                                    ("item", uid)));
                }

                return(false);
            }

            // Is it.. actually in one of their hands?
            if (!_handsSystem.IsHolding(user, uid, out _, hands))
            {
                if (!quiet)
                {
                    user.PopupMessage(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)));
                }

                return(false);
            }

            // Seems legit.
            return(true);
        }
Esempio n. 12
0
        public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
        {
            if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
                component.SubscribedPilots.Contains(pilotComponent))
            {
                return;
            }

            if (TryComp <SharedEyeComponent>(entity, out var eye))
            {
                eye.Zoom = component.Zoom;
            }

            component.SubscribedPilots.Add(pilotComponent);

            _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);

            entity.PopupMessage(Loc.GetString("shuttle-pilot-start"));
            pilotComponent.Console  = component;
            pilotComponent.Position = EntityManager.GetComponent <TransformComponent>(entity).Coordinates;
            pilotComponent.Dirty();
        }
Esempio n. 13
0
        /// <summary>
        ///     Checks that an entity and a set of map coordinates 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="origin">The entity to use.</param>
        /// <param name="other">The map coordinates 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 <see cref="origin"/> or <see cref="other"/> 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 origin entity for
        ///     it to see.
        /// </param>
        /// <returns>
        ///     True if the two points are within a given range without being obstructed.
        /// </returns>
        public bool InRangeUnobstructed(
            EntityUid origin,
            MapCoordinates other,
            float range = InteractionRange,
            CollisionGroup collisionMask = CollisionGroup.Impassable,
            Ignored?predicate            = null,
            bool ignoreInsideBlocker     = false,
            bool popup = false)
        {
            var originPosition = Transform(origin).MapPosition;

            predicate ??= e => e == origin;

            var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);

            if (!inRange && popup)
            {
                var message = Loc.GetString("shared-interaction-system-in-range-unobstructed-cannot-reach");
                origin.PopupMessage(message);
            }

            return(inRange);
        }
 /// <summary>
 ///     Pops up a message at the given entity's location for it alone to see.
 /// </summary>
 /// <param name="viewer">The entity that will see the message.</param>
 /// <param name="message">The message to be seen.</param>
 public static void PopupMessage(this EntityUid viewer, string message)
 {
     viewer.PopupMessage(viewer, message);
 }
Esempio n. 15
0
 /// <summary>
 ///     Pops up a message at the given entity's location for everyone,
 ///     including itself, to see.
 /// </summary>
 /// <param name="source">The entity above which to show the message.</param>
 /// <param name="message">The message to be seen.</param>
 /// <param name="playerManager">
 ///     The instance of player manager to use, will be resolved automatically
 ///     if null.
 /// </param>
 /// <param name="range">
 ///     The range in which to search for players, defaulting to one screen.
 /// </param>
 public static void PopupMessageEveryone(this EntityUid source, string message, IPlayerManager?playerManager = null, int range = 15)
 {
     source.PopupMessage(message);
     source.PopupMessageOtherClients(message);
 }
Esempio n. 16
0
    /// <summary>
    /// Tries to fire a round of ammo out of the weapon.
    /// </summary>
    private void TryFire(EntityUid user, EntityCoordinates targetCoords, ServerRangedWeaponComponent gun)
    {
        if (!TryComp(gun.Owner, out ServerRangedBarrelComponent? barrel))
        {
            return;
        }

        if (!TryComp(user, out HandsComponent? hands) || hands.ActiveHand?.HeldEntity != gun.Owner)
        {
            return;
        }

        if (!TryComp(user, out CombatModeComponent? combat) ||
            !combat.IsInCombatMode ||
            !_blocker.CanInteract(user, gun.Owner))
        {
            return;
        }

        var fireAttempt = new GunFireAttemptEvent(user, gun);

        EntityManager.EventBus.RaiseLocalEvent(gun.Owner, fireAttempt);

        if (fireAttempt.Cancelled)
        {
            return;
        }

        var curTime = _gameTiming.CurTime;
        var span    = curTime - gun.LastFireTime;

        if (span.TotalSeconds < 1 / barrel.FireRate)
        {
            return;
        }

        // TODO: Clumsy should be eventbus I think?

        gun.LastFireTime = curTime;
        var coordinates = Transform(gun.Owner).Coordinates;

        if (gun.ClumsyCheck && EntityManager.TryGetComponent <ClumsyComponent>(user, out var clumsyComponent) && ClumsyComponent.TryRollClumsy(user, gun.ClumsyExplodeChance))
        {
            //Wound them
            _damageable.TryChangeDamage(user, clumsyComponent.ClumsyDamage);
            _stun.TryParalyze(user, TimeSpan.FromSeconds(3f), true);

            // Apply salt to the wound ("Honk!")
            SoundSystem.Play(
                Filter.Pvs(gun.Owner), gun.ClumsyWeaponHandlingSound.GetSound(),
                coordinates, AudioParams.Default.WithMaxDistance(5));

            SoundSystem.Play(
                Filter.Pvs(gun.Owner), gun.ClumsyWeaponShotSound.GetSound(),
                coordinates, AudioParams.Default.WithMaxDistance(5));

            user.PopupMessage(Loc.GetString("server-ranged-weapon-component-try-fire-clumsy"));

            EntityManager.DeleteEntity(gun.Owner);
            return;
        }

        // Firing confirmed

        if (gun.CanHotspot)
        {
            _atmos.HotspotExpose(coordinates, 700, 50);
        }

        EntityManager.EventBus.RaiseLocalEvent(gun.Owner, new GunShotEvent());
        Fire(user, barrel, targetCoords);
    }
    // Handles logic for our different types of valid target.
    // Checks for conditions that would prevent a doAfter from starting.
    private void HandleDoAfter(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, FixedPoint2 currentVolume, FixedPoint2 availableVolume)
    {
        // Below variables will be set within this function depending on what kind of target was clicked.
        // They will be passed to the OnTransferComplete if the doAfter succeeds.

        EntityUid donor;
        EntityUid acceptor;
        string    donorSolutionName;
        string    acceptorSolutionName;

        FixedPoint2 transferAmount;

        var            delay = 1.0f; //default do_after delay in seconds.
        string         msg;
        SoundSpecifier sfx;

        // For our purposes, if our target has a PuddleComponent, treat it as a puddle above all else.
        if (TryComp <PuddleComponent>(target, out var puddle))
        {
            // These return conditions will abort BEFORE the do_after is called:
            if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || // puddle Solution is null
                (puddleSolution.TotalVolume <= 0))    // puddle is completely empty
            {
                return;
            }
            else if (availableVolume < 0) // mop is completely full
            {
                msg = "mopping-system-tool-full";
                user.PopupMessage(user, Loc.GetString(msg, ("used", used))); // play message now because we are aborting.
                return;
            }
            // adding to puddles
            else if (puddleSolution.TotalVolume < component.MopLowerLimit && // if the puddle is too small for the tool to effectively absorb any more solution from it
                     currentVolume > 0)   // tool needs a solution to dilute the puddle with.
            {
                // Dilutes the puddle with some solution from the tool
                transferAmount = FixedPoint2.Max(component.ResidueAmount, currentVolume);
                TryTransfer(used, target, "absorbed", puddle.SolutionName, transferAmount); // Complete the transfer right away, with no doAfter.

                sfx = component.TransferSound;
                SoundSystem.Play(sfx.GetSound(), Filter.Pvs(user), used); // Give instant feedback for diluting puddle, so that it's clear that the player is adding to the puddle (as opposed to other behaviours, which have a doAfter).

                msg = "mopping-system-puddle-diluted";
                user.PopupMessage(user, Loc.GetString(msg)); // play message now because we are aborting.

                return;                                      // Do not begin a doAfter.
            }
            else
            {
                // Taking from puddles:

                // Determine transferAmount:
                transferAmount = FixedPoint2.Min(component.PickupAmount, puddleSolution.TotalVolume, availableVolume);

                // TODO: consider onelining this with the above, using additional args on Min()?
                if ((puddleSolution.TotalVolume - transferAmount) < component.MopLowerLimit) // If the transferAmount would bring the puddle below the MopLowerLimit
                {
                    transferAmount = puddleSolution.TotalVolume - component.MopLowerLimit;   // Then the transferAmount should bring the puddle down to the MopLowerLimit exactly
                }

                donor             = target; // the puddle Uid
                donorSolutionName = puddle.SolutionName;

                acceptor             = used;       // the mop/tool Uid
                acceptorSolutionName = "absorbed"; // by definition on AbsorbentComponent

                // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter.
                delay = (component.PickupAmount.Float() / 10.0f) * component.MopSpeed; // Delay should scale with PickupAmount, which represents the maximum we can pick up per click.
                msg   = "mopping-system-puddle-success";
                sfx   = component.PickupSound;

                DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx);
            }
        }
        else if ((TryComp <RefillableSolutionComponent>(target, out var refillable)) && // We can put solution from the tool into the target
                 (currentVolume > 0))                                               // And the tool is wet
        {
            // These return conditions will abort BEFORE the do_after is called:
            if (!_solutionSystem.TryGetRefillableSolution(target, out var refillableSolution)) // refillable Solution is null
            {
                return;
            }
            else if (refillableSolution.AvailableVolume <= 0) // target container is full (liquid destination)
            {
                msg = "mopping-system-target-container-full";
                user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting.
                return;
            }
            else if (refillableSolution.MaxVolume <= FixedPoint2.New(20)) // target container is too small (e.g. syringe)
            {
                msg = "mopping-system-target-container-too-small";
                user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting.
                return;
            }
            else
            {
                // Determine transferAmount
                if (_tagSystem.HasTag(used, "Mop") && // if the tool used is a literal mop (and not a sponge, rag, etc.)
                    !_tagSystem.HasTag(target, "Wringer"))                                                         // and if the target does not have a wringer for properly drying the mop
                {
                    delay = 5.0f;                                                                                  // Should take much longer if you don't have a wringer

                    if ((currentVolume / (currentVolume + availableVolume)) > 0.25)                                // mop is more than one-quarter full
                    {
                        transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume * 0.6); // squeeze up to 60% of the solution from the mop.
                        msg            = "mopping-system-hand-squeeze-little-wet";

                        if ((currentVolume / (currentVolume + availableVolume)) > 0.5) // if the mop is more than half full
                        {
                            msg = "mopping-system-hand-squeeze-still-wet";             // overwrites the above
                        }
                    }
                    else // mop is less than one-quarter full
                    {
                        transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); // squeeze remainder of solution from the mop.
                        msg            = "mopping-system-hand-squeeze-dry";
                    }
                }
                else
                {
                    transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); //Transfer all liquid from the tool to the container, but only if it will fit.
                    msg            = "mopping-system-refillable-success";
                    delay          = 1.0f;
                }

                donor             = used;       // the mop/tool Uid
                donorSolutionName = "absorbed"; // by definition on AbsorbentComponent

                acceptor             = target;  // the refillable container's Uid
                acceptorSolutionName = refillable.Solution;

                // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter.

                sfx = component.TransferSound;

                DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx);
            }
        }
Esempio n. 18
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);
                }
            }
        }
Esempio n. 19
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);
            }
        }
        public bool TryDoInject(EntityUid?target, EntityUid user)
        {
            if (!EligibleEntity(target, _entMan))
            {
                return(false);
            }

            string?msgFormat = null;

            if (target == user)
            {
                msgFormat = "hypospray-component-inject-self-message";
            }
            else if (EligibleEntity(user, _entMan) && ClumsyComponent.TryRollClumsy(user, ClumsyFailChance))
            {
                msgFormat = "hypospray-component-inject-self-clumsy-message";
                target    = user;
            }

            var solutionsSys = EntitySystem.Get <SolutionContainerSystem>();

            solutionsSys.TryGetSolution(Owner, SolutionName, out var hypoSpraySolution);

            if (hypoSpraySolution == null || hypoSpraySolution.CurrentVolume == 0)
            {
                user.PopupMessageCursor(Loc.GetString("hypospray-component-empty-message"));
                return(true);
            }

            if (!solutionsSys.TryGetInjectableSolution(target.Value, out var targetSolution))
            {
                user.PopupMessage(user,
                                  Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target.Value, _entMan))));
                return(false);
            }

            user.PopupMessage(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message",
                                            ("other", target)));
            if (target != user)
            {
                target.Value.PopupMessage(Loc.GetString("hypospray-component-feel-prick-message"));
                var meleeSys = EntitySystem.Get <MeleeWeaponSystem>();
                var angle    = Angle.FromWorldVec(_entMan.GetComponent <TransformComponent>(target.Value).WorldPosition - _entMan.GetComponent <TransformComponent>(user).WorldPosition);
                meleeSys.SendLunge(angle, user);
            }

            SoundSystem.Play(_injectSound.GetSound(), Filter.Pvs(user), user);

            // Get transfer amount. May be smaller than _transferAmount if not enough room
            var realTransferAmount = FixedPoint2.Min(TransferAmount, targetSolution.AvailableVolume);

            if (realTransferAmount <= 0)
            {
                user.PopupMessage(user,
                                  Loc.GetString("hypospray-component-transfer-already-full-message",
                                                ("owner", target)));
                return(true);
            }

            // Move units from attackSolution to targetSolution
            var removedSolution =
                EntitySystem.Get <SolutionContainerSystem>()
                .SplitSolution(Owner, hypoSpraySolution, realTransferAmount);

            if (!targetSolution.CanAddSolution(removedSolution))
            {
                return(true);
            }

            removedSolution.DoEntityReaction(target.Value, ReactionMethod.Injection);

            EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(target.Value, targetSolution, removedSolution);