예제 #1
0
        /// <summary>
        /// Handle adding keys to the ignition, give stuff the InVehicleComponent so it can't be picked
        /// up by people not in the vehicle.
        /// </summary>
        private void OnEntInserted(EntityUid uid, VehicleComponent component, EntInsertedIntoContainerMessage args)
        {
            var inVehicle = AddComp <InVehicleComponent>(args.Entity);

            inVehicle.Vehicle = component;

            if (_tagSystem.HasTag(args.Entity, "VehicleKey"))
            {
                // Return if the slot is not the key slot
                // That slot ID should be inherited from basevehicle in the .yml
                if (args.Container.ID != "key_slot")
                {
                    return;
                }

                // This lets the vehicle move
                EnsureComp <SharedPlayerInputMoverComponent>(uid);
                // This lets the vehicle open doors
                if (component.HasRider)
                {
                    _tagSystem.AddTag(uid, "DoorBumpOpener");
                }

                component.HasKey = true;

                // Audiovisual feedback
                _ambientSound.SetAmbience(uid, true);
            }
        }
예제 #2
0
 private void OnActivateUIAttempt(EntityUid uid, DroneComponent component, UserOpenActivatableUIAttemptEvent args)
 {
     if (!component.ApplyLaws)
     {
         return;
     }
     if (!_tagSystem.HasTag(args.Target, "DroneUsable"))
     {
         args.Cancel();
     }
 }
예제 #3
0
        /// <summary>
        /// For now pilots just interact with the console and can start piloting with wasd.
        /// </summary>
        private void HandleConsoleInteract(EntityUid uid, ShuttleConsoleComponent component, ActivateInWorldEvent args)
        {
            if (!_tags.HasTag(args.User, "CanPilot"))
            {
                return;
            }

            var pilotComponent = EntityManager.EnsureComponent <PilotComponent>(args.User);

            if (!component.Enabled)
            {
                args.User.PopupMessage($"Console is not powered.");
                return;
            }

            args.Handled = true;
            var console = pilotComponent.Console;

            if (console != null)
            {
                RemovePilot(pilotComponent);

                if (console == component)
                {
                    return;
                }
            }

            AddPilot(args.User, component);
        }
 private void HandleGeneratorCollide(EntityUid uid, ContainmentFieldGeneratorComponent component, StartCollideEvent args)
 {
     if (_tags.HasTag(args.OtherFixture.Body.Owner, "EmitterBolt"))
     {
         ReceivePower(6, component);
     }
 }
예제 #5
0
        private void OnAfterInteract(EntityUid uid, EmagComponent component, AfterInteractEvent args)
        {
            if (!args.CanReach || args.Target == null)
            {
                return;
            }

            if (_tagSystem.HasTag(args.Target.Value, "EmagImmune"))
            {
                return;
            }

            if (component.Charges <= 0)
            {
                _popupSystem.PopupEntity(Loc.GetString("emag-no-charges"), args.User, Filter.Entities(args.User));
                return;
            }

            var emaggedEvent = new GotEmaggedEvent(args.User);

            RaiseLocalEvent(args.Target.Value, emaggedEvent, false);
            if (emaggedEvent.Handled)
            {
                _popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User,
                                         Filter.Entities(args.User), PopupType.Medium);
                _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(args.User):player} emagged {ToPrettyString(args.Target.Value):target}");
                component.Charges--;
                return;
            }
        }
        private bool TryPilot(EntityUid user, EntityUid uid)
        {
            if (!_tags.HasTag(user, "CanPilot") ||
                !TryComp <ShuttleConsoleComponent>(uid, out var component) ||
                !this.IsPowered(uid, EntityManager) ||
                !Transform(uid).Anchored ||
                !_blocker.CanInteract(user, uid))
            {
                return(false);
            }

            var pilotComponent = EntityManager.EnsureComponent <PilotComponent>(user);
            var console        = pilotComponent.Console;

            if (console != null)
            {
                RemovePilot(pilotComponent);

                if (console == component)
                {
                    return(false);
                }
            }

            AddPilot(user, component);
            return(true);
        }
예제 #7
0
        private void OnInteractUsing(EntityUid uid, PaperComponent paperComp, InteractUsingEvent args)
        {
            if (_tagSystem.HasTag(args.Used, "Write"))
            {
                if (!TryComp <ActorComponent>(args.User, out var actor))
                {
                    return;
                }

                paperComp.Mode = PaperAction.Write;
                UpdateUserInterface(uid, paperComp);
                _uiSystem.GetUiOrNull(uid, PaperUiKey.Key)?.Open(actor.PlayerSession);
                return;
            }

            // If a stamp, attempt to stamp paper
            if (TryComp <StampComponent>(args.Used, out var stampComp) && TryStamp(uid, stampComp.StampedName, stampComp.StampState, paperComp))
            {
                // successfully stamped, play popup
                var stampPaperOtherMessage = Loc.GetString("paper-component-action-stamp-paper-other", ("user", args.User), ("target", args.Target), ("stamp", args.Used));
                _popupSystem.PopupEntity(stampPaperOtherMessage, args.User, Filter.Pvs(args.User, entityManager: EntityManager).RemoveWhereAttachedEntity(puid => puid == args.User));
                var stampPaperSelfMessage = Loc.GetString("paper-component-action-stamp-paper-self", ("target", args.Target), ("stamp", args.Used));
                _popupSystem.PopupEntity(stampPaperSelfMessage, args.User, Filter.Entities(args.User));
            }
        }
예제 #8
0
        public bool Suicide(EntityUid victim)
        {
            // Checks to see if the CannotSuicide tag exits, ghosts instead.
            if (_tagSystem.HasTag(victim, "CannotSuicide"))
            {
                return(false);
            }

            // Checks to see if the player is dead.
            if (!TryComp <MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState))
            {
                return(false);
            }

            _adminLogger.Add(LogType.Suicide,
                             $"{EntityManager.ToPrettyString(victim):player} is committing suicide");

            var suicideEvent = new SuicideEvent(victim);

            // If you are critical, you wouldn't be able to use your surroundings to suicide, so you do the default suicide
            if (!_mobState.IsCritical(victim, mobState))
            {
                EnvironmentSuicideHandler(victim, suicideEvent);
            }
            DefaultSuicideHandler(victim, suicideEvent);

            ApplyDeath(victim, suicideEvent.Kind !.Value);
            return(true);
        }
예제 #9
0
    public IEnumerable <EntityUid> GetAllPayloads(EntityUid uid, ContainerManagerComponent?contMan = null)
    {
        if (!Resolve(uid, ref contMan, false))
        {
            yield break;
        }

        foreach (var container in contMan.Containers.Values)
        {
            foreach (var entity in container.ContainedEntities)
            {
                if (_tagSystem.HasTag(entity, "Payload"))
                {
                    yield return(entity);
                }
            }
        }
    }
예제 #10
0
    private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args)
    {
        if (!TryComp(uid, out ContainerManagerComponent? contMan))
        {
            return;
        }

        // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs.
        foreach (var container in contMan.Containers.Values)
        {
            foreach (var entity in container.ContainedEntities)
            {
                if (_tagSystem.HasTag(entity, "Payload"))
                {
                    RaiseLocalEvent(entity, args, false);
                }
            }
        }
    }
        /// <summary>
        /// Handle adding keys to the ignition, give stuff the InVehicleComponent so it can't be picked
        /// up by people not in the vehicle.
        /// </summary>
        private void OnEntInserted(EntityUid uid, VehicleComponent component, EntInsertedIntoContainerMessage args)
        {
            if (args.Container.ID != KeySlot ||
                !_tagSystem.HasTag(args.Entity, "VehicleKey"))
            {
                return;
            }

            // Enable vehicle
            var inVehicle = AddComp <InVehicleComponent>(args.Entity);

            inVehicle.Vehicle = component;

            component.HasKey = true;

            // Audiovisual feedback
            _ambientSound.SetAmbience(uid, true);
            _tagSystem.AddTag(uid, "DoorBumpOpener");
            _modifier.RefreshMovementSpeedModifiers(uid);
        }
예제 #12
0
        private void Recycle(RecyclerComponent component, EntityUid entity)
        {
            RecyclableComponent?recyclable = null;

            // Can only recycle things that are recyclable... And also check the safety of the thing to recycle.
            if (!_tags.HasTag(entity, "Recyclable") &&
                (!TryComp(entity, out recyclable) || !recyclable.Safe && component.Safe))
            {
                return;
            }

            // TODO: Prevent collision with recycled items

            // Mobs are a special case!
            if (CanGib(component, entity))
            {
                Comp <SharedBodyComponent>(entity).Gib(true);
                Bloodstain(component);
                return;
            }

            if (recyclable == null)
            {
                QueueDel(entity);
            }
            else
            {
                Recycle(recyclable, component.Efficiency);
            }

            if (component.Sound != null && (_timing.CurTime - component.LastSound).TotalSeconds > RecyclerSoundCooldown)
            {
                SoundSystem.Play(Filter.Pvs(component.Owner, entityManager: EntityManager), component.Sound.GetSound(), component.Owner, AudioHelpers.WithVariation(0.01f).WithVolume(-3));
                component.LastSound = _timing.CurTime;
            }
        }
예제 #13
0
    /// <summary>
    ///     Tries to throw the entity if it has a physics component, otherwise does nothing.
    /// </summary>
    /// <param name="entity">The entity being thrown.</param>
    /// <param name="direction">A vector pointing from the entity to its destination.</param>
    /// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
    /// <param name="user"></param>
    /// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
    public void TryThrow(
        EntityUid uid,
        Vector2 direction,
        float strength               = 1.0f,
        EntityUid?user               = null,
        float pushbackRatio          = 10.0f,
        PhysicsComponent?physics     = null,
        TransformComponent?transform = null,
        EntityQuery <PhysicsComponent>?physicsQuery = null,
        EntityQuery <TransformComponent>?xformQuery = null)
    {
        if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero)
        {
            return;
        }

        physicsQuery ??= GetEntityQuery <PhysicsComponent>();
        if (physics == null && !physicsQuery.Value.TryGetComponent(uid, out physics))
        {
            return;
        }

        if (physics.BodyType != BodyType.Dynamic)
        {
            Logger.Warning($"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!");
            return;
        }

        var comp = EnsureComp <ThrownItemComponent>(uid);

        comp.Thrower = user;
        // Give it a l'il spin.
        if (!_tagSystem.HasTag(uid, "NoSpinOnThrow"))
        {
            physics.ApplyAngularImpulse(ThrowAngularImpulse);
        }
        else
        {
            if (transform == null)
            {
                xformQuery ??= GetEntityQuery <TransformComponent>();
                transform = xformQuery.Value.GetComponent(uid);
            }
            transform.LocalRotation = direction.ToWorldAngle() - Math.PI;
        }

        if (user != null)
        {
            _interactionSystem.ThrownInteraction(user.Value, uid);
        }

        var impulseVector = direction.Normalized * strength * physics.Mass;

        physics.ApplyLinearImpulse(impulseVector);

        // Estimate time to arrival so we can apply OnGround status and slow it much faster.
        var time = (direction / strength).Length;

        if (time < FlyTime)
        {
            physics.BodyStatus = BodyStatus.OnGround;
            _thrownSystem.LandComponent(comp);
        }
        else
        {
            physics.BodyStatus = BodyStatus.InAir;

            Timer.Spawn(TimeSpan.FromSeconds(time - FlyTime), () =>
            {
                if (physics.Deleted)
                {
                    return;
                }
                physics.BodyStatus = BodyStatus.OnGround;
                _thrownSystem.LandComponent(comp);
            });
        }

        // Give thrower an impulse in the other direction
        if (user != null && pushbackRatio > 0.0f && physicsQuery.Value.TryGetComponent(user.Value, out var userPhysics))
        {
            var msg = new ThrowPushbackAttemptEvent();
            RaiseLocalEvent(physics.Owner, msg, false);

            if (!msg.Cancelled)
            {
                userPhysics.ApplyLinearImpulse(-impulseVector * pushbackRatio);
            }
        }
    }
예제 #14
0
        public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
                                        float siemens = 1,
                                        ElectrifiedComponent?electrified     = null,
                                        NodeContainerComponent?nodeContainer = null,
                                        TransformComponent?transform         = null)
        {
            if (!Resolve(uid, ref electrified, ref transform, false))
            {
                return(false);
            }

            if (!electrified.Enabled)
            {
                return(false);
            }

            if (electrified.NoWindowInTile)
            {
                foreach (var entity in transform.Coordinates.GetEntitiesInTile(
                             LookupFlags.Approximate | LookupFlags.IncludeAnchored, _entityLookup))
                {
                    if (_tagSystem.HasTag(entity, "Window"))
                    {
                        return(false);
                    }
                }
            }

            siemens *= electrified.SiemensCoefficient;
            if (!DoCommonElectrocutionAttempt(targetUid, uid, ref siemens) || siemens <= 0)
            {
                return(false); // If electrocution would fail, do nothing.
            }
            var targets = new List <(EntityUid entity, int depth)>();

            GetChainedElectrocutionTargets(targetUid, targets);
            if (!electrified.RequirePower)
            {
                var lastRet = true;
                for (var i = targets.Count - 1; i >= 0; i--)
                {
                    var(entity, depth) = targets[i];
                    lastRet            = TryDoElectrocution(
                        entity,
                        uid,
                        (int)(electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)),
                        TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)), true,
                        electrified.SiemensCoefficient);
                }

                return(lastRet);
            }

            if (!Resolve(uid, ref nodeContainer, false))
            {
                return(false);
            }

            var node = TryNode(electrified.HighVoltageNode) ??
                       TryNode(electrified.MediumVoltageNode) ??
                       TryNode(electrified.LowVoltageNode);

            if (node == null)
            {
                return(false);
            }

            var(damageMult, timeMult) = node.NodeGroupID switch
            {
                NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier),
                NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier,
                                        electrified.MediumVoltageTimeMultiplier),
                _ => (1f, 1f)
            };

            {
                var lastRet = true;
                for (var i = targets.Count - 1; i >= 0; i--)
                {
                    var(entity, depth) = targets[i];
                    lastRet            = TryDoElectrocutionPowered(
                        entity,
                        uid,
                        node,
                        (int)(electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult),
                        TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) *
                                             timeMult), true,
                        electrified.SiemensCoefficient);
                }

                return(lastRet);
            }


            Node?TryNode(string?id)
            {
                if (id != null && nodeContainer.TryGetNode <Node>(id, out var tryNode) &&
                    tryNode.NodeGroup is IBasePowerNet {
                    NetworkNode : { LastAvailableSupplySum : > 0 }
                })
                {
                    return(tryNode);
                }

                return(null);
            }
    // 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);
            }
        }
        private void OnInteractUsing(EntityUid uid, PlantHolderComponent component, InteractUsingEvent args)
        {
            if (TryComp(args.Used, out SeedComponent? seeds))
            {
                if (component.Seed == null)
                {
                    if (!_botanySystem.TryGetSeed(seeds, out var seed))
                    {
                        return;
                    }

                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message",
                                                           ("seedName", seed.Name),
                                                           ("seedNoun", seed.Noun)), Filter.Entities(args.User), PopupType.Medium);

                    component.Seed      = seed;
                    component.Dead      = false;
                    component.Age       = 1;
                    component.Health    = component.Seed.Endurance;
                    component.LastCycle = _gameTiming.CurTime;

                    EntityManager.QueueDeleteEntity(args.Used);

                    component.CheckLevelSanity();
                    component.UpdateSprite();

                    return;
                }

                _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message",
                                                       ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium);
                return;
            }

            if (_tagSystem.HasTag(args.Used, "Hoe"))
            {
                if (component.WeedLevel > 0)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message",
                                                           ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium);
                    _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message",
                                                           ("otherName", Comp <MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User));
                    component.WeedLevel = 0;
                    component.UpdateSprite();
                }
                else
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), Filter.Entities(args.User));
                }

                return;
            }

            if (_tagSystem.HasTag(args.Used, "Shovel"))
            {
                if (component.Seed != null)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message",
                                                           ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium);
                    _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message",
                                                           ("name", Comp <MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User));
                    component.RemovePlant();
                }
                else
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
                                                           ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User));
                }

                return;
            }

            if (_solutionSystem.TryGetDrainableSolution(args.Used, out var solution) &&
                _solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var targetSolution) && TryComp(args.Used, out SprayComponent? spray))
            {
                var amount = FixedPoint2.New(1);

                var targetEntity   = uid;
                var solutionEntity = args.Used;


                SoundSystem.Play(spray.SpraySound.GetSound(), Filter.Pvs(args.Used),
                                 args.Used, AudioHelpers.WithVariation(0.125f));


                var split = _solutionSystem.Drain(solutionEntity, solution, amount);

                if (split.TotalVolume == 0)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
                                                           ("owner", args.Used)), Filter.Entities(args.User));
                    return;
                }

                _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-spray-message",
                                                       ("owner", uid),
                                                       ("amount", split.TotalVolume)), Filter.Entities(args.User), PopupType.Medium);

                _solutionSystem.TryAddSolution(targetEntity, targetSolution, split);

                component.ForceUpdateByExternalCause();

                return;
            }

            if (_tagSystem.HasTag(args.Used, "PlantSampleTaker"))
            {
                if (component.Seed == null)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), Filter.Entities(args.User));
                    return;
                }

                if (component.Sampled)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), Filter.Entities(args.User));
                    return;
                }

                if (component.Dead)
                {
                    _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), Filter.Entities(args.User));
                    return;
                }

                component.Seed.Unique = false;
                var seed = _botanySystem.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates);
                seed.RandomOffset(0.25f);
                _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
                                                       ("seedName", component.Seed.DisplayName)), Filter.Entities(args.User));
                component.Health -= (_random.Next(3, 5) * 10);

                if (_random.Prob(0.3f))
                {
                    component.Sampled = true;
                }

                // Just in case.
                component.CheckLevelSanity();
                component.ForceUpdateByExternalCause();

                return;
            }

            if (HasComp <SharpComponent>(args.Used))
            {
                component.DoHarvest(args.User);
            }

            if (TryComp <ProduceComponent?>(args.Used, out var produce))
            {
                _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-compost-message",
                                                       ("owner", uid),
                                                       ("usingItem", args.Used)), Filter.Entities(args.User), PopupType.Medium);
                _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message",
                                                       ("user", Identity.Entity(args.User, EntityManager)),
                                                       ("usingItem", args.Used),
                                                       ("owner", uid)), uid, Filter.PvsExcept(args.User));

                if (_solutionSystem.TryGetSolution(args.Used, produce.SolutionName, out var solution2))
                {
                    // This deliberately discards overfill.
                    _solutionSystem.TryAddSolution(args.Used, solution2,
                                                   _solutionSystem.SplitSolution(args.Used, solution2, solution2.TotalVolume));

                    component.ForceUpdateByExternalCause();
                }

                EntityManager.QueueDeleteEntity(args.Used);
            }
        }
    private void OnInteractUsing(EntityUid uid, MachineFrameComponent component, InteractUsingEvent args)
    {
        if (!component.HasBoard && TryComp <MachineBoardComponent?>(args.Used, out var machineBoard))
        {
            if (args.Used.TryRemoveFromContainer())
            {
                // Valid board!
                component.BoardContainer.Insert(args.Used);

                // Setup requirements and progress...
                ResetProgressAndRequirements(component, machineBoard);

                if (TryComp <AppearanceComponent?>(uid, out var appearance))
                {
                    appearance.SetData(MachineFrameVisuals.State, 2);
                }

                if (TryComp(uid, out ConstructionComponent? construction))
                {
                    // So prying the components off works correctly.
                    _construction.ResetEdge(uid, construction);
                }
            }
        }
        else if (component.HasBoard)
        {
            if (TryComp <MachinePartComponent>(args.Used, out var machinePart))
            {
                if (!component.Requirements.ContainsKey(machinePart.PartType))
                {
                    return;
                }

                if (component.Progress[machinePart.PartType] != component.Requirements[machinePart.PartType] &&
                    args.Used.TryRemoveFromContainer() && component.PartContainer.Insert(args.Used))
                {
                    component.Progress[machinePart.PartType]++;
                    args.Handled = true;
                    return;
                }
            }

            if (TryComp <StackComponent?>(args.Used, out var stack))
            {
                var type = stack.StackTypeId;
                if (!component.MaterialRequirements.ContainsKey(type))
                {
                    return;
                }

                if (component.MaterialProgress[type] == component.MaterialRequirements[type])
                {
                    return;
                }

                var needed = component.MaterialRequirements[type] - component.MaterialProgress[type];
                var count  = stack.Count;

                if (count < needed)
                {
                    if (!component.PartContainer.Insert(stack.Owner))
                    {
                        return;
                    }

                    component.MaterialProgress[type] += count;
                    args.Handled = true;
                    return;
                }

                var splitStack = _stack.Split(args.Used, needed,
                                              Comp <TransformComponent>(uid).Coordinates, stack);

                if (splitStack == null)
                {
                    return;
                }

                if (!component.PartContainer.Insert(splitStack.Value))
                {
                    return;
                }

                component.MaterialProgress[type] += needed;
                args.Handled = true;
                return;
            }

            foreach (var(compName, info) in component.ComponentRequirements)
            {
                if (component.ComponentProgress[compName] >= info.Amount)
                {
                    continue;
                }

                var registration = _factory.GetRegistration(compName);

                if (!HasComp(args.Used, registration.Type))
                {
                    continue;
                }

                if (!args.Used.TryRemoveFromContainer() || !component.PartContainer.Insert(args.Used))
                {
                    continue;
                }
                component.ComponentProgress[compName]++;
                args.Handled = true;
                return;
            }

            foreach (var(tagName, info) in component.TagRequirements)
            {
                if (component.TagProgress[tagName] >= info.Amount)
                {
                    continue;
                }

                if (!_tag.HasTag(args.Used, tagName))
                {
                    continue;
                }

                if (!args.Used.TryRemoveFromContainer() || !component.PartContainer.Insert(args.Used))
                {
                    continue;
                }
                component.TagProgress[tagName]++;
                args.Handled = true;
                return;
            }
        }
    }
예제 #18
0
        private bool IsRCDStillValid(RCDComponent rcd, AfterInteractEvent eventArgs, IMapGrid mapGrid, TileRef tile, RcdMode startingMode)
        {
            //Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed.
            if (rcd.CurrentAmmo <= 0)
            {
                _popup.PopupEntity(Loc.GetString("rcd-component-no-ammo-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                return(false);
            }

            if (rcd.Mode != startingMode)
            {
                return(false);
            }

            var unobstructed = eventArgs.Target == null
                ? _interactionSystem.InRangeUnobstructed(eventArgs.User, mapGrid.GridTileToWorld(tile.GridIndices), popup : true)
                : _interactionSystem.InRangeUnobstructed(eventArgs.User, eventArgs.Target.Value, popup: true);

            if (!unobstructed)
            {
                return(false);
            }

            switch (rcd.Mode)
            {
            //Floor mode just needs the tile to be a space tile (subFloor)
            case RcdMode.Floors:
                if (!tile.Tile.IsEmpty)
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-floor-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }

                return(true);

            //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
            case RcdMode.Deconstruct:
                if (tile.Tile.IsEmpty)
                {
                    return(false);
                }

                //They tried to decon a turf but the turf is blocked
                if (eventArgs.Target == null && tile.IsBlockedTurf(true))
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }
                //They tried to decon a non-turf but it's not in the whitelist
                if (eventArgs.Target != null && !_tagSystem.HasTag(eventArgs.Target.Value, "RCDDeconstructWhitelist"))
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }

                return(true);

            //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code.
            case RcdMode.Walls:
                if (tile.Tile.IsEmpty)
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-wall-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }

                if (tile.IsBlockedTurf(true))
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }
                return(true);

            case RcdMode.Airlock:
                if (tile.Tile.IsEmpty)
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-airlock-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }
                if (tile.IsBlockedTurf(true))
                {
                    _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User));
                    return(false);
                }
                return(true);

            default:
                return(false);    //I don't know why this would happen, but sure I guess. Get out of here invalid state!
            }
        }
예제 #19
0
        /// <summary>
        ///     Get all of the entities in an area for displaying on the context menu.
        /// </summary>
        public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List <EntityUid>?result)
        {
            result = null;

            if (_stateManager.CurrentState is not GameScreenBase gameScreenBase)
            {
                return(false);
            }

            var player = _playerManager.LocalPlayer?.ControlledEntity;

            if (player == null)
            {
                return(false);
            }

            // If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks.
            var visibility = _eyeManager.CurrentEye.DrawFov
                ? Visibility
                : Visibility | MenuVisibility.NoFov;

            // Do we have to do FoV checks?
            if ((visibility & MenuVisibility.NoFov) == 0)
            {
                var entitiesUnderMouse = gameScreenBase.GetEntitiesUnderPosition(targetPos);
                bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e);

                if (!_examineSystem.CanExamine(player.Value, targetPos, Predicate))
                {
                    return(false);
                }
            }

            // Get entities
            var entities = _entityLookup.GetEntitiesInRange(targetPos.MapId, targetPos.Position, EntityMenuLookupSize)
                           .ToList();

            if (entities.Count == 0)
            {
                return(false);
            }

            if (visibility == MenuVisibility.All)
            {
                result = entities;
                return(true);
            }

            // remove any entities in containers
            if ((visibility & MenuVisibility.InContainer) == 0)
            {
                foreach (var entity in entities.ToList())
                {
                    if (!ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity))
                    {
                        entities.Remove(entity);
                    }
                }
            }

            // remove any invisible entities
            if ((visibility & MenuVisibility.Invisible) == 0)
            {
                foreach (var entity in entities.ToList())
                {
                    if (!EntityManager.TryGetComponent(entity, out ISpriteComponent? spriteComponent) ||
                        !spriteComponent.Visible)
                    {
                        entities.Remove(entity);
                        continue;
                    }

                    if (_tagSystem.HasTag(entity, "HideContextMenu"))
                    {
                        entities.Remove(entity);
                    }
                }
            }

            // Remove any entities that do not have LOS
            if ((visibility & MenuVisibility.NoFov) == 0)
            {
                var playerPos = EntityManager.GetComponent <TransformComponent>(player.Value).MapPosition;
                foreach (var entity in entities.ToList())
                {
                    if (!ExamineSystemShared.InRangeUnOccluded(
                            playerPos,
                            EntityManager.GetComponent <TransformComponent>(entity).MapPosition,
                            ExamineSystemShared.ExamineRange,
                            null))
                    {
                        entities.Remove(entity);
                    }
                }
            }

            if (entities.Count == 0)
            {
                return(false);
            }

            result = entities;
            return(true);
        }