private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args)
        {
            if (!args.CanAccess || !args.CanInteract)
            {
                return;
            }

            if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution))
            {
                return;
            }

            if (solution.DrainAvailable == ReagentUnit.Zero)
            {
                return;
            }

            Verb verb = new();

            verb.Text = Loc.GetString("spill-target-verb-get-data-text");
            // TODO VERB ICONS spill icon? pouring out a glass/beaker?
            verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target.Uid,
                                                                    solution, solution.DrainAvailable).SpillAt(args.Target.Transform.Coordinates, "PuddleSmear");
            args.Verbs.Add(verb);
        }
    private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetVerbsEvent <Verb> args)
    {
        if (!args.CanAccess || !args.CanInteract)
        {
            return;
        }

        if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target, out var solution))
        {
            return;
        }

        if (solution.DrainAvailable == FixedPoint2.Zero)
        {
            return;
        }

        Verb verb = new();

        verb.Text = Loc.GetString("spill-target-verb-get-data-text");
        // TODO VERB ICONS spill icon? pouring out a glass/beaker?
        verb.Act = () =>
        {
            var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target,
                                                                        solution, solution.DrainAvailable);
            SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear");
        };
        verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
        args.Verbs.Add(verb);
    }
Example #3
0
    private void HandleChemicalPayloadTrigger(EntityUid uid, ChemicalPayloadComponent component, TriggerEvent args)
    {
        if (component.BeakerSlotA.Item is not EntityUid beakerA ||
            component.BeakerSlotB.Item is not EntityUid beakerB ||
            !TryComp(beakerA, out FitsInDispenserComponent? compA) ||
            !TryComp(beakerB, out FitsInDispenserComponent? compB) ||
            !_solutionSystem.TryGetSolution(beakerA, compA.Solution, out var solutionA) ||
            !_solutionSystem.TryGetSolution(beakerB, compB.Solution, out var solutionB) ||
            solutionA.TotalVolume == 0 ||
            solutionB.TotalVolume == 0)
        {
            return;
        }

        var solStringA = SolutionContainerSystem.ToPrettyString(solutionA);
        var solStringB = SolutionContainerSystem.ToPrettyString(solutionB);

        _logSystem.Add(LogType.ChemicalReaction,
                       $"Chemical bomb payload {ToPrettyString(uid):payload} at {Transform(uid).MapPosition:location} is combining two solutions: {solStringA:solutionA} and {solStringB:solutionB}");

        solutionA.MaxVolume += solutionB.MaxVolume;
        _solutionSystem.TryAddSolution(beakerA, solutionA, solutionB);
        solutionB.RemoveAllSolution();

        // The grenade might be a dud. Redistribute solution:
        var tmpSol = _solutionSystem.SplitSolution(beakerA, solutionA, solutionA.CurrentVolume * solutionB.MaxVolume / solutionA.MaxVolume);

        _solutionSystem.TryAddSolution(beakerB, solutionB, tmpSol);
        solutionA.MaxVolume -= solutionB.MaxVolume;
        _solutionSystem.UpdateChemicals(beakerA, solutionA, false);
    }
        private void OnFire(EntityUid uid, ChemicalAmmoComponent component, AmmoShotEvent args)
        {
            if (!_solutionSystem.TryGetSolution(uid, component.SolutionName, out var ammoSolution))
            {
                return;
            }

            var projectiles = args.FiredProjectiles;

            var projectileSolutionContainers = new List <(EntityUid, Solution)>();

            foreach (var projectile in projectiles)
            {
                if (_solutionSystem
                    .TryGetSolution(projectile, component.SolutionName, out var projectileSolutionContainer))
                {
                    projectileSolutionContainers.Add((uid, projectileSolutionContainer));
                }
            }

            if (!projectileSolutionContainers.Any())
            {
                return;
            }

            var solutionPerProjectile = ammoSolution.CurrentVolume * (1 / projectileSolutionContainers.Count);

            foreach (var(projectileUid, projectileSolution) in projectileSolutionContainers)
            {
                var solutionToTransfer = _solutionSystem.SplitSolution(uid, ammoSolution, solutionPerProjectile);
                _solutionSystem.TryAddSolution(projectileUid, projectileSolution, solutionToTransfer);
            }

            _solutionSystem.RemoveAllSolution(uid, ammoSolution);
        }
Example #5
0
        private bool TrySliceFood(EntityUid uid, EntityUid user, EntityUid usedItem,
                                  SliceableFoodComponent?component = null, FoodComponent?food = null, TransformComponent?transform = null)
        {
            if (!Resolve(uid, ref component, ref food, ref transform) ||
                string.IsNullOrEmpty(component.Slice))
            {
                return(false);
            }

            if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var solution))
            {
                return(false);
            }

            if (!EntityManager.TryGetComponent(usedItem, out UtensilComponent ? utensil) || (utensil.Types & UtensilType.Knife) == 0)
            {
                return(false);
            }

            var sliceUid = EntityManager.SpawnEntity(component.Slice, transform.Coordinates);

            var lostSolution = _solutionContainerSystem.SplitSolution(uid, solution,
                                                                      solution.CurrentVolume / FixedPoint2.New(component.Count));

            // Fill new slice
            FillSlice(sliceUid, lostSolution);

            if (EntityManager.TryGetComponent(user, out HandsComponent? handsComponent))
            {
                if (ContainerHelpers.IsInContainer(component.Owner))
                {
                    handsComponent.PutInHandOrDrop(EntityManager.GetComponent <ItemComponent>(sliceUid));
                }
            }

            SoundSystem.Play(Filter.Pvs(uid), component.Sound.GetSound(), transform.Coordinates,
                             AudioParams.Default.WithVolume(-2));

            component.Count--;
            // If someone makes food proto with 1 slice...
            if (component.Count < 1)
            {
                EntityManager.DeleteEntity(uid);
                return(true);
            }

            // Split last slice
            if (component.Count == 1)
            {
                var lastSlice = EntityManager.SpawnEntity(component.Slice, transform.Coordinates);

                // Fill last slice with the rest of the solution
                FillSlice(lastSlice, solution);

                EntityManager.DeleteEntity(uid);
            }

            return(true);
        }
        /// <summary>
        /// Will overflow this entity to neighboring entities if required
        /// </summary>
        private void CheckOverflow(PuddleComponent puddleComponent)
        {
            if (puddleComponent.CurrentVolume <= puddleComponent.OverflowVolume ||
                puddleComponent.Overflown)
            {
                return;
            }

            var nextPuddles = new List <PuddleComponent>()
            {
                puddleComponent
            };
            var overflownPuddles = new List <PuddleComponent>();

            while (puddleComponent.OverflowLeft > FixedPoint2.Zero && nextPuddles.Count > 0)
            {
                foreach (var next in nextPuddles.ToArray())
                {
                    nextPuddles.Remove(next);

                    next.Overflown = true;
                    overflownPuddles.Add(next);

                    var adjacentPuddles = GetAllAdjacentOverflow(next).ToArray();
                    if (puddleComponent.OverflowLeft <= FixedPoint2.Epsilon * adjacentPuddles.Length)
                    {
                        break;
                    }

                    if (adjacentPuddles.Length == 0)
                    {
                        continue;
                    }

                    var numberOfAdjacent = FixedPoint2.New(adjacentPuddles.Length);
                    var overflowSplit    = puddleComponent.OverflowLeft / numberOfAdjacent;
                    foreach (var adjacent in adjacentPuddles)
                    {
                        var adjacentPuddle = adjacent();
                        var quantity       = FixedPoint2.Min(overflowSplit, adjacentPuddle.OverflowVolume);
                        var puddleSolution = _solutionContainerSystem.EnsureSolution(puddleComponent.Owner,
                                                                                     puddleComponent.SolutionName);
                        var spillAmount = _solutionContainerSystem.SplitSolution(puddleComponent.Owner,
                                                                                 puddleSolution, quantity);

                        TryAddSolution(adjacentPuddle.Owner, spillAmount, false, false);
                        nextPuddles.Add(adjacentPuddle);
                    }
                }
            }

            foreach (var puddle in overflownPuddles)
            {
                puddle.Overflown = false;
            }
        }
    private void ReleaseToFloor(EntityCoordinates clickLocation, AbsorbentComponent absorbent, Solution?absorbedSolution)
    {
        if ((_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid)) && // needs valid grid
            absorbedSolution is not null)    // needs a solution to place on the tile
        {
            TileRef tile = mapGrid.GetTileRef(clickLocation);

            // Drop some of the absorbed liquid onto the ground
            var releaseAmount    = FixedPoint2.Min(absorbent.ResidueAmount, absorbedSolution.CurrentVolume);        // The release amount specified on the absorbent component, or the amount currently absorbed (whichever is less).
            var releasedSolution = _solutionSystem.SplitSolution(absorbent.Owner, absorbedSolution, releaseAmount); // Remove releaseAmount of solution from the absorbent component
            _spillableSystem.SpillAt(tile, releasedSolution, puddlePrototypeId);                                    // And spill it onto the tile.
        }
    }
        public override void Update(float frameTime)
        {
            base.Update(frameTime);
            var queueDelete = new RemQueue <EvaporationComponent>();

            foreach (var evaporationComponent in EntityManager.EntityQuery <EvaporationComponent>())
            {
                var uid = evaporationComponent.Owner;
                evaporationComponent.Accumulator += frameTime;

                if (!_solutionContainerSystem.TryGetSolution(uid, evaporationComponent.SolutionName, out var solution))
                {
                    // If no solution, delete the entity
                    EntityManager.QueueDeleteEntity(uid);
                    continue;
                }

                if (evaporationComponent.Accumulator < evaporationComponent.EvaporateTime)
                {
                    continue;
                }

                evaporationComponent.Accumulator -= evaporationComponent.EvaporateTime;

                if (evaporationComponent.EvaporationToggle == true)
                {
                    _solutionContainerSystem.SplitSolution(uid, solution,
                                                           FixedPoint2.Min(FixedPoint2.New(1), solution.CurrentVolume)); // removes 1 unit, or solution current volume, whichever is lower.
                }

                if (solution.CurrentVolume <= 0)
                {
                    EntityManager.QueueDeleteEntity(uid);
                }
                else if (solution.CurrentVolume <= evaporationComponent.LowerLimit || // if puddle is too big or too small to evaporate.
                         solution.CurrentVolume >= evaporationComponent.UpperLimit)
                {
                    evaporationComponent.EvaporationToggle = false; // pause evaporation
                }
                else
                {
                    evaporationComponent.EvaporationToggle = true;  // unpause evaporation, e.g. if a puddle previously above evaporation UpperLimit was brought down below evaporation UpperLimit via mopping.
                }
            }

            foreach (var evaporationComponent in queueDelete)
            {
                EntityManager.RemoveComponent(evaporationComponent.Owner, evaporationComponent);
            }
        }
Example #9
0
        public override void Update(float frameTime)
        {
            base.Update(frameTime);
            var queueDelete = new RemQueue <EvaporationComponent>();

            foreach (var evaporationComponent in EntityManager.EntityQuery <EvaporationComponent>())
            {
                var uid = evaporationComponent.Owner;
                evaporationComponent.Accumulator += frameTime;

                if (!_solutionContainerSystem.TryGetSolution(uid, evaporationComponent.SolutionName, out var solution))
                {
                    // If no solution, delete the entity
                    queueDelete.Add(evaporationComponent);
                    continue;
                }

                if (evaporationComponent.Accumulator < evaporationComponent.EvaporateTime)
                {
                    continue;
                }

                evaporationComponent.Accumulator -= evaporationComponent.EvaporateTime;


                _solutionContainerSystem.SplitSolution(uid, solution,
                                                       FixedPoint2.Min(FixedPoint2.New(1), solution.CurrentVolume));

                if (solution.CurrentVolume == 0)
                {
                    EntityManager.QueueDeleteEntity(uid);
                }
                else if (solution.CurrentVolume <= evaporationComponent.LowerLimit ||
                         solution.CurrentVolume >= evaporationComponent.UpperLimit)
                {
                    queueDelete.Add(evaporationComponent);
                }
            }

            foreach (var evaporationComponent in queueDelete)
            {
                EntityManager.RemoveComponent(evaporationComponent.Owner, evaporationComponent);
            }
        }
        private void OnFeed(EntityUid uid, SharedBodyComponent body, FeedEvent args)
        {
            if (args.Food.Deleted)
            {
                return;
            }

            args.Food.CancelToken = null;

            if (!_bodySystem.TryGetComponentsOnMechanisms <StomachComponent>(uid, out var stomachs, body))
            {
                return;
            }

            var transferAmount = args.Food.TransferAmount != null
                ? FixedPoint2.Min((FixedPoint2)args.Food.TransferAmount, args.FoodSolution.CurrentVolume)
                : args.FoodSolution.CurrentVolume;

            var split        = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount);
            var firstStomach = stomachs.FirstOrNull(
                stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split));

            var forceFeed = uid != args.User;

            // No stomach so just popup a message that they can't eat.
            if (firstStomach == null)
            {
                _solutionContainerSystem.TryAddSolution(uid, args.FoodSolution, split);
                _popupSystem.PopupEntity(
                    forceFeed ?
                    Loc.GetString("food-system-you-cannot-eat-any-more-other") :
                    Loc.GetString("food-system-you-cannot-eat-any-more")
                    , uid, Filter.Entities(args.User));
                return;
            }

            split.DoEntityReaction(uid, ReactionMethod.Ingestion);
            _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);

            if (forceFeed)
            {
                EntityManager.TryGetComponent(uid, out MetaDataComponent? targetMeta);
                var targetName = targetMeta?.EntityName ?? string.Empty;

                EntityManager.TryGetComponent(args.User, out MetaDataComponent? userMeta);
                var userName = userMeta?.EntityName ?? string.Empty;

                _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName)),
                                         uid, Filter.Entities(uid));

                _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)),
                                         args.User, Filter.Entities(args.User));
            }
            else
            {
                _popupSystem.PopupEntity(Loc.GetString(args.Food.EatMessage, ("food", args.Food.Owner)), args.User, Filter.Entities(args.User));
            }

            SoundSystem.Play(Filter.Pvs(uid), args.Food.UseSound.GetSound(), uid, AudioParams.Default.WithVolume(-1f));

            // Try to break all used utensils
            foreach (var utensil in args.Utensils)
            {
                _utensilSystem.TryBreak((utensil).Owner, args.User);
            }

            if (args.Food.UsesRemaining > 0)
            {
                return;
            }

            if (string.IsNullOrEmpty(args.Food.TrashPrototype))
            {
                EntityManager.QueueDeleteEntity(args.Food.Owner);
            }
            else
            {
                DeleteAndSpawnTrash(args.Food, args.User);
            }
        }
    private void OnAfterInteract(EntityUid uid, SprayComponent component, AfterInteractEvent args)
    {
        if (args.Handled)
        {
            return;
        }

        args.Handled = true;

        if (!_solutionContainerSystem.TryGetSolution(uid, SprayComponent.SolutionName, out var solution))
        {
            return;
        }

        var ev = new SprayAttemptEvent(args.User);

        RaiseLocalEvent(uid, ev, false);
        if (ev.Cancelled)
        {
            return;
        }

        var curTime = _gameTiming.CurTime;

        if (TryComp <ItemCooldownComponent>(uid, out var cooldown) &&
            curTime < cooldown.CooldownEnd)
        {
            return;
        }

        if (solution.CurrentVolume <= 0)
        {
            _popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), uid,
                                     Filter.Entities(args.User));
            return;
        }

        var playerPos = Transform(args.User).Coordinates;

        if (args.ClickLocation.GetGridUid(EntityManager) != playerPos.GetGridUid(EntityManager))
        {
            return;
        }

        var direction     = (args.ClickLocation.Position - playerPos.Position).Normalized;
        var threeQuarters = direction * 0.75f;
        var quarter       = direction * 0.25f;

        var amount = Math.Max(Math.Min((solution.CurrentVolume / component.TransferAmount).Int(), component.VaporAmount), 1);

        var spread = component.VaporSpread / amount;

        for (var i = 0; i < amount; i++)
        {
            var rotation = new Angle(direction.ToAngle() + Angle.FromDegrees(spread * i) -
                                     Angle.FromDegrees(spread * (amount - 1) / 2));

            var(_, diffPos) = args.ClickLocation - playerPos;
            var diffNorm   = diffPos.Normalized;
            var diffLength = diffPos.Length;

            var target = Transform(args.User).Coordinates
                         .Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter);

            if (target.TryDistance(EntityManager, playerPos, out var distance) && distance > component.SprayDistance)
            {
                target = Transform(args.User).Coordinates
                         .Offset(diffNorm * component.SprayDistance);
            }

            var newSolution = _solutionContainerSystem.SplitSolution(uid, solution, component.TransferAmount);

            if (newSolution.TotalVolume <= FixedPoint2.Zero)
            {
                break;
            }

            var vapor = Spawn(component.SprayedPrototype,
                              playerPos.Offset(distance < 1 ? quarter : threeQuarters));
            Transform(vapor).LocalRotation = rotation;

            if (TryComp(vapor, out AppearanceComponent? appearance))
            {
                appearance.SetData(VaporVisuals.Color, solution.Color.WithAlpha(1f));
                appearance.SetData(VaporVisuals.State, true);
            }

            // Add the solution to the vapor and actually send the thing
            var vaporComponent = Comp <VaporComponent>(vapor);
            _vaporSystem.TryAddSolution(vaporComponent, newSolution);

            // impulse direction is defined in world-coordinates, not local coordinates
            var impulseDirection = Transform(vapor).WorldRotation.ToVec();
            _vaporSystem.Start(vaporComponent, impulseDirection, component.SprayVelocity, target, component.SprayAliveTime);

            if (component.Impulse > 0f && TryComp(args.User, out PhysicsComponent? body))
            {
                body.ApplyLinearImpulse(-impulseDirection * component.Impulse);
            }
        }

        SoundSystem.Play(component.SpraySound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.125f));

        RaiseLocalEvent(uid,
                        new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(component.CooldownTime)), true);
    }
        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);
            }
        }
        /// <summary>
        /// Tries to eat some food
        /// </summary>
        /// <param name="uid">Food entity.</param>
        /// <param name="user">Feeding initiator.</param>
        /// <returns>True if an interaction occurred (i.e., food was consumed, or a pop-up message was created)</returns>
        public bool TryUseFood(EntityUid uid, EntityUid user, FoodComponent?food = null)
        {
            if (!Resolve(uid, ref food))
            {
                return(false);
            }

            // if currently being used to force-feed, cancel that action.
            if (food.CancelToken != null)
            {
                food.CancelToken.Cancel();
                food.CancelToken = null;
                return(true);
            }

            if (uid == user ||                                                                                  //Suppresses self-eating
                EntityManager.TryGetComponent <MobStateComponent>(uid, out var mobState) && mobState.IsAlive()) // Suppresses eating alive mobs
            {
                return(false);
            }

            if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var solution))
            {
                return(false);
            }

            if (food.UsesRemaining <= 0)
            {
                _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", uid)), user, Filter.Entities(user));
                DeleteAndSpawnTrash(food, user);
                return(true);
            }

            if (!EntityManager.TryGetComponent(user, out SharedBodyComponent ? body) ||
                !_bodySystem.TryGetComponentsOnMechanisms <StomachComponent>(user, out var stomachs, body))
            {
                return(false);
            }

            if (IsMouthBlocked(user, user))
            {
                return(true);
            }

            var usedUtensils = new List <UtensilComponent>();

            if (!TryGetRequiredUtensils(user, food, out var utensils))
            {
                return(true);
            }

            if (!user.InRangeUnobstructed(uid, popup: true))
            {
                return(true);
            }

            var transferAmount = food.TransferAmount != null?FixedPoint2.Min((FixedPoint2)food.TransferAmount, solution.CurrentVolume) : solution.CurrentVolume;

            var split        = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
            var firstStomach = stomachs.FirstOrNull(
                stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split));

            if (firstStomach == null)
            {
                _solutionContainerSystem.TryAddSolution(uid, solution, split);
                _popupSystem.PopupEntity(Loc.GetString("food-system-you-cannot-eat-any-more"), user, Filter.Entities(user));
                return(true);
            }

            // TODO: Account for partial transfer.
            split.DoEntityReaction(user, ReactionMethod.Ingestion);
            _stomachSystem.TryTransferSolution((firstStomach.Value.Comp).Owner, split, firstStomach.Value.Comp);

            SoundSystem.Play(Filter.Pvs(user), food.UseSound.GetSound(), user, AudioParams.Default.WithVolume(-1f));
            _popupSystem.PopupEntity(Loc.GetString(food.EatMessage, ("food", food.Owner)), user, Filter.Entities(user));

            // Try to break all used utensils
            foreach (var utensil in usedUtensils)
            {
                _utensilSystem.TryBreak((utensil).Owner, user);
            }

            if (food.UsesRemaining > 0)
            {
                return(true);
            }

            if (string.IsNullOrEmpty(food.TrashPrototype))
            {
                EntityManager.QueueDeleteEntity((food).Owner);
            }
            else
            {
                DeleteAndSpawnTrash(food, user);
            }

            return(true);
        }
Example #14
0
    private void OnAfterInteract(EntityUid uid, SprayComponent component, AfterInteractEvent args)
    {
        if (args.Handled)
        {
            return;
        }

        args.Handled = true;

        if (!_solutionContainerSystem.TryGetSolution(uid, SprayComponent.SolutionName, out var solution))
        {
            return;
        }

        var ev = new SprayAttemptEvent(args.User);

        RaiseLocalEvent(uid, ev, false);
        if (ev.Cancelled)
        {
            return;
        }

        var curTime = _gameTiming.CurTime;

        if (TryComp <ItemCooldownComponent>(uid, out var cooldown) &&
            curTime < cooldown.CooldownEnd)
        {
            return;
        }

        if (solution.CurrentVolume <= 0)
        {
            _popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), uid,
                                     Filter.Entities(args.User));
            return;
        }

        var xformQuery = GetEntityQuery <TransformComponent>();
        var userXform  = xformQuery.GetComponent(args.User);

        var userMapPos  = userXform.MapPosition;
        var clickMapPos = args.ClickLocation.ToMap(EntityManager);

        var diffPos = clickMapPos.Position - userMapPos.Position;

        if (diffPos == Vector2.Zero || diffPos == Vector2.NaN)
        {
            return;
        }

        var diffLength = diffPos.Length;
        var diffNorm   = diffPos.Normalized;
        var diffAngle  = diffNorm.ToAngle();

        // Vectors to determine the spawn offset of the vapor clouds.
        var threeQuarters = diffNorm * 0.75f;
        var quarter       = diffNorm * 0.25f;

        var amount = Math.Max(Math.Min((solution.CurrentVolume / component.TransferAmount).Int(), component.VaporAmount), 1);
        var spread = component.VaporSpread / amount;

        for (var i = 0; i < amount; i++)
        {
            var rotation = new Angle(diffAngle + Angle.FromDegrees(spread * i) -
                                     Angle.FromDegrees(spread * (amount - 1) / 2));

            // Calculate the destination for the vapor cloud. Limit to the maximum spray distance.
            var target = userMapPos
                         .Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter);

            var distance = target.Position.Length;
            if (distance > component.SprayDistance)
            {
                target = userMapPos.Offset(diffNorm * component.SprayDistance);
            }

            var newSolution = _solutionContainerSystem.SplitSolution(uid, solution, component.TransferAmount);

            if (newSolution.TotalVolume <= FixedPoint2.Zero)
            {
                break;
            }

            // Spawn the vapor cloud onto the grid/map the user is present on. Offset the start position based on how far the target destination is.
            var vaporPos   = userMapPos.Offset(distance < 1 ? quarter : threeQuarters);
            var vapor      = Spawn(component.SprayedPrototype, vaporPos);
            var vaporXform = xformQuery.GetComponent(vapor);

            vaporXform.WorldRotation = rotation;

            if (TryComp(vapor, out AppearanceComponent? appearance))
            {
                appearance.SetData(VaporVisuals.Color, solution.Color.WithAlpha(1f));
                appearance.SetData(VaporVisuals.State, true);
            }

            // Add the solution to the vapor and actually send the thing
            var vaporComponent = Comp <VaporComponent>(vapor);
            _vaporSystem.TryAddSolution(vaporComponent, newSolution);

            // impulse direction is defined in world-coordinates, not local coordinates
            var impulseDirection = rotation.ToVec();
            _vaporSystem.Start(vaporComponent, vaporXform, impulseDirection, component.SprayVelocity, target, component.SprayAliveTime, args.User);
        }

        SoundSystem.Play(component.SpraySound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.125f));

        RaiseLocalEvent(uid,
                        new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(component.CooldownTime)), true);
    }
        public override void Update(float frameTime)
        {
            base.Update(frameTime);
            foreach (var drain in EntityQuery <DrainComponent>())
            {
                drain.Accumulator += frameTime;
                if (drain.Accumulator < drain.DrainFrequency)
                {
                    continue;
                }
                drain.Accumulator -= drain.DrainFrequency;

                /// Best to do this one every second rather than once every tick...
                _solutionSystem.TryGetSolution(drain.Owner, DrainComponent.SolutionName, out var drainSolution);

                if (drainSolution is null)
                {
                    return;
                }

                /// Remove a bit from the buffer
                _solutionSystem.SplitSolution(drain.Owner, drainSolution, (drain.UnitsDestroyedPerSecond * drain.DrainFrequency));

                /// This will ensure that UnitsPerSecond is per second...
                var amount = drain.UnitsPerSecond * drain.DrainFrequency;
                var xform  = Transform(drain.Owner);
                List <PuddleComponent> puddles = new();

                foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, drain.Range))
                {
                    /// No InRangeUnobstructed because there's no collision group that fits right now
                    /// and these are placed by mappers and not buildable/movable so shouldnt really be a problem...
                    if (TryComp <PuddleComponent>(entity, out var puddleComp))
                    {
                        puddles.Add(puddleComp);
                    }
                }

                if (puddles.Count == 0)
                {
                    _ambientSoundSystem.SetAmbience(drain.Owner, false);
                    continue;
                }

                _ambientSoundSystem.SetAmbience(drain.Owner, true);

                amount /= puddles.Count;

                foreach (var puddle in puddles)
                {
                    /// Queue the solution deletion if it's empty. EvaporationSystem might also do this
                    /// but queuedelete should be pretty safe.
                    if (!_solutionSystem.TryGetSolution(puddle.Owner, puddle.SolutionName, out var puddleSolution))
                    {
                        EntityManager.QueueDeleteEntity(puddle.Owner);
                        continue;
                    }

                    /// Removes the lowest of:
                    /// the drain component's units per second adjusted for # of puddles
                    /// the puddle's remaining volume (making it cleanly zero)
                    /// the drain's remaining volume in its buffer.
                    var transferSolution = _solutionSystem.SplitSolution(puddle.Owner, puddleSolution,
                                                                         FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.CurrentVolume, drainSolution.AvailableVolume));

                    _solutionSystem.TryAddSolution(drain.Owner, drainSolution, transferSolution);

                    if (puddleSolution.CurrentVolume <= 0)
                    {
                        EntityManager.QueueDeleteEntity(puddle.Owner);
                    }
                }
            }
        }