/// <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 TryDraw(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user)
    {
        if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) ||
            solution.AvailableVolume == 0)
        {
            return;
        }

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

        if (realTransferAmount <= 0)
        {
            _popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", targetEntity)),
                               component.Owner, Filter.Entities(user));
            return;
        }

        // Move units from attackSolution to targetSolution
        var removedSolution = _solutions.Draw(targetEntity, targetSolution, realTransferAmount);

        if (!_solutions.TryAddSolution(component.Owner, solution, removedSolution))
        {
            return;
        }

        _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
                                         ("amount", removedSolution.TotalVolume),
                                         ("target", targetEntity)), component.Owner, Filter.Entities(user));

        Dirty(component);
        AfterDraw(component);
    }
    private void TryInjectIntoBloodstream(InjectorComponent component, BloodstreamComponent targetBloodstream, EntityUid user)
    {
        // Get transfer amount. May be smaller than _transferAmount if not enough room
        var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetBloodstream.ChemicalSolution.AvailableVolume);

        if (realTransferAmount <= 0)
        {
            _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", targetBloodstream.Owner)),
                               component.Owner, Filter.Entities(user));
            return;
        }

        // Move units from attackSolution to targetSolution
        var removedSolution = _solutions.SplitSolution(user, targetBloodstream.ChemicalSolution, realTransferAmount);

        _blood.TryAddToChemicals((targetBloodstream).Owner, removedSolution, targetBloodstream);

        removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);

        _popup.PopupEntity(Loc.GetString("injector-component-inject-success-message",
                                         ("amount", removedSolution.TotalVolume),
                                         ("target", targetBloodstream.Owner)), component.Owner, Filter.Entities(user));

        Dirty(component);
        AfterInject(component);
    }
        /// <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.
        }
    }
Beispiel #6
0
        public void FixedPoint2Min()
        {
            var unorderedList = new[]
            {
                FixedPoint2.New(5),
                FixedPoint2.New(3),
                FixedPoint2.New(1),
                FixedPoint2.New(2),
                FixedPoint2.New(4),
            };
            var min = FixedPoint2.Min(unorderedList);

            Assert.That(min, Is.EqualTo(FixedPoint2.New(1)));
        }
Beispiel #7
0
        /// <summary>
        ///     Raised directed at a victim when someone has force fed them a drink.
        /// </summary>
        private void OnForceDrink(EntityUid uid, SharedBodyComponent body, ForceDrinkEvent args)
        {
            if (args.Drink.Deleted)
            {
                return;
            }

            args.Drink.CancelToken = null;
            var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
            var drained        = _solutionContainerSystem.Drain((args.Drink).Owner, args.DrinkSolution, transferAmount);

            if (!_bodySystem.TryGetComponentsOnMechanisms <StomachComponent>(uid, out var stomachs, body))
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-cannot-drink-other"),
                                         uid, Filter.Entities(args.User));

                _spillableSystem.SpillAt(uid, drained, "PuddleSmear");
                return;
            }

            var firstStomach = stomachs.FirstOrNull(
                stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, drained));

            // All stomach are full or can't handle whatever solution we have.
            if (firstStomach == null)
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"),
                                         uid, Filter.Entities(args.User));

                _spillableSystem.SpillAt(uid, drained, "PuddleSmear");
                return;
            }

            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("drink-component-force-feed-success", ("user", userName)),
                                     uid, Filter.Entities(uid));

            _popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed-success-user", ("target", targetName)),
                                     args.User, Filter.Entities(args.User));

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

            drained.DoEntityReaction(uid, ReactionMethod.Ingestion);
            _stomachSystem.TryTransferSolution((firstStomach.Value.Comp).Owner, drained, firstStomach.Value.Comp);
        }
        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);
            }
        }
    private void OnAfterInteract(EntityUid uid, FireExtinguisherComponent component, AfterInteractEvent args)
    {
        if (args.Target == null || !args.CanReach)
        {
            return;
        }

        if (args.Handled)
        {
            return;
        }

        args.Handled = true;

        if (component.HasSafety && component.Safety)
        {
            _popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), uid,
                                     Filter.Entities(args.User));
            return;
        }

        if (args.Target is not {
            Valid : true
        } target ||
            !_solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution) ||
            !_solutionContainerSystem.TryGetRefillableSolution(uid, out var container))
        {
            return;
        }

        var transfer = container.AvailableVolume;

        if (TryComp <SolutionTransferComponent>(uid, out var solTrans))
        {
            transfer = solTrans.TransferAmount;
        }
        transfer = FixedPoint2.Min(transfer, targetSolution.DrainAvailable);

        if (transfer > 0)
        {
            var drained = _solutionContainerSystem.Drain(target, targetSolution, transfer);
            _solutionContainerSystem.TryAddSolution(uid, container, drained);

            SoundSystem.Play(Filter.Pvs(uid), component.RefillSound.GetSound(), uid);
            _popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-after-interact-refilled-message", ("owner", uid)),
                                     uid, Filter.Entities(args.Target.Value));
        }
    }
Beispiel #10
0
        protected override void ReactWithEntity(EntityUid entity, double solutionFraction)
        {
            if (!EntitySystem.Get <SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
            {
                return;
            }

            if (!_entMan.TryGetComponent(entity, out BloodstreamComponent? bloodstream))
            {
                return;
            }

            var invSystem = EntitySystem.Get <InventorySystem>();

            // TODO: Add a permeability property to clothing
            // For now it just adds to protection for each clothing equipped
            var protection = 0f;

            if (invSystem.TryGetSlots(entity, out var slotDefinitions))
            {
                foreach (var slot in slotDefinitions)
                {
                    if (slot.Name == "back" ||
                        slot.Name == "pocket1" ||
                        slot.Name == "pocket2" ||
                        slot.Name == "id")
                    {
                        continue;
                    }

                    if (invSystem.TryGetSlotEntity(entity, slot.Name, out _))
                    {
                        protection += 0.025f;
                    }
                }
            }

            var bloodstreamSys = EntitySystem.Get <BloodstreamSystem>();

            var cloneSolution  = solution.Clone();
            var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection),
                                                 bloodstream.Solution.AvailableVolume);
            var transferSolution = cloneSolution.SplitSolution(transferAmount);

            bloodstreamSys.TryAddToBloodstream(entity, transferSolution, bloodstream);
        }
Beispiel #11
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);
            }
        }
Beispiel #12
0
        protected override void ReactWithEntity(EntityUid entity, double solutionFraction)
        {
            if (!EntitySystem.Get <SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
            {
                return;
            }

            if (!_entMan.TryGetComponent(entity, out BloodstreamComponent? bloodstream))
            {
                return;
            }

            // TODO: Add a permeability property to clothing
            // For now it just adds to protection for each clothing equipped
            var protection = 0f;

            if (_entMan.TryGetComponent(entity, out InventoryComponent? inventory))
            {
                foreach (var slot in inventory.Slots)
                {
                    if (slot == EquipmentSlotDefines.Slots.BACKPACK ||
                        slot == EquipmentSlotDefines.Slots.POCKET1 ||
                        slot == EquipmentSlotDefines.Slots.POCKET2 ||
                        slot == EquipmentSlotDefines.Slots.IDCARD)
                    {
                        continue;
                    }

                    if (inventory.TryGetSlotItem(slot, out ItemComponent? _))
                    {
                        protection += 0.025f;
                    }
                }
            }

            var bloodstreamSys = EntitySystem.Get <BloodstreamSystem>();

            var cloneSolution  = solution.Clone();
            var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection),
                                                 bloodstream.Solution.AvailableVolume);
            var transferSolution = cloneSolution.SplitSolution(transferAmount);

            bloodstreamSys.TryAddToBloodstream(entity, transferSolution, bloodstream);
        }
        public void TryAddSolution(Solution solution)
        {
            if (solution.TotalVolume == 0)
            {
                return;
            }

            if (!EntitySystem.Get <SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solutionArea))
            {
                return;
            }

            var addSolution =
                solution.SplitSolution(FixedPoint2.Min(solution.TotalVolume, solutionArea.AvailableVolume));

            EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(Owner, solutionArea, addSolution);

            UpdateVisuals();
        }
    private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
    {
        if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) ||
            solution.CurrentVolume == 0)
        {
            return;
        }

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

        if (realTransferAmount <= 0)
        {
            _popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", targetEntity)),
                               component.Owner, Filter.Entities(user));
            return;
        }

        // Move units from attackSolution to targetSolution
        var removedSolution = _solutions.SplitSolution(component.Owner, solution, realTransferAmount);

        removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);

        if (!asRefill)
        {
            _solutions.Inject(targetEntity, targetSolution, removedSolution);
        }
        else
        {
            _solutions.Refill(targetEntity, targetSolution, removedSolution);
        }

        _popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message",
                                         ("amount", removedSolution.TotalVolume),
                                         ("target", targetEntity)), component.Owner, Filter.Entities(user));

        Dirty(component);
        AfterInject(component);
    }
        async Task <bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
        {
            var solutionContainerSystem = EntitySystem.Get <SolutionContainerSystem>();

            if (eventArgs.Target == null || !eventArgs.CanReach)
            {
                if (_hasSafety && _safety)
                {
                    Owner.PopupMessage(eventArgs.User, Loc.GetString("fire-extinguisher-component-safety-on-message"));
                    return(true);
                }
                return(false);
            }

            if (eventArgs.Target is not {
                Valid : true
            } target ||
                !_entMan.HasComponent <ReagentTankComponent>(target) ||
                !solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution) ||
                !solutionContainerSystem.TryGetDrainableSolution(Owner, out var container))
            {
                return(false);
            }

            var transfer = FixedPoint2.Min(container.AvailableVolume, targetSolution.DrainAvailable);

            if (transfer > 0)
            {
                var drained = solutionContainerSystem.Drain(target, targetSolution, transfer);
                solutionContainerSystem.TryAddSolution(Owner, container, drained);

                SoundSystem.Play(Filter.Pvs(Owner), _refillSound.GetSound(), Owner);
                eventArgs.Target.Value.PopupMessage(eventArgs.User,
                                                    Loc.GetString("fire-extingusiher-component-after-interact-refilled-message", ("owner", Owner)));
            }

            return(true);
        }
        protected override void ReactWithEntity(EntityUid entity, double solutionFraction)
        {
            if (!EntitySystem.Get <SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
            {
                return;
            }

            if (!_entMan.TryGetComponent(entity, out BloodstreamComponent? bloodstream))
            {
                return;
            }

            if (_entMan.TryGetComponent(entity, out InternalsComponent? internals) &&
                internals.AreInternalsWorking())
            {
                return;
            }

            var chemistry        = EntitySystem.Get <ReactiveSystem>();
            var cloneSolution    = solution.Clone();
            var transferAmount   = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.Solution.AvailableVolume);
            var transferSolution = cloneSolution.SplitSolution(transferAmount);

            foreach (var reagentQuantity in transferSolution.Contents.ToArray())
            {
                if (reagentQuantity.Quantity == FixedPoint2.Zero)
                {
                    continue;
                }
                chemistry.ReactionEntity(entity, ReactionMethod.Ingestion, reagentQuantity.ReagentId, reagentQuantity.Quantity, transferSolution);
            }

            var bloodstreamSys = EntitySystem.Get <BloodstreamSystem>();

            bloodstreamSys.TryAddToBloodstream(entity, transferSolution, bloodstream);
        }
        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);
            }
        }
        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);
                    }
                }
            }
        }
        async Task <bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
        {
            if (!Powered)
            {
                Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-no-power"));
                return(false);
            }

            if (_broken)
            {
                Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-broken"));
                return(false);
            }

            if (_entities.GetComponent <HandsComponent>(eventArgs.User).GetActiveHand?.Owner is not {
                Valid: true
            } itemEntity)
            {
                eventArgs.User.PopupMessage(Loc.GetString("microwave-component-interact-using-no-active-hand"));
                return(false);
            }

            if (_entities.TryGetComponent <SolutionTransferComponent?>(itemEntity, out var attackPourable))
            {
                var solutionsSystem = EntitySystem.Get <SolutionContainerSystem>();
                if (!solutionsSystem.TryGetDrainableSolution(itemEntity, out var attackSolution))
                {
                    return(false);
                }

                if (!solutionsSystem.TryGetSolution(Owner, SolutionName, out var solution))
                {
                    return(false);
                }

                //Get transfer amount. May be smaller than _transferAmount if not enough room
                var realTransferAmount = FixedPoint2.Min(attackPourable.TransferAmount, solution.AvailableVolume);
                if (realTransferAmount <= 0) //Special message if container is full
                {
                    Owner.PopupMessage(eventArgs.User,
                                       Loc.GetString("microwave-component-interact-using-container-full"));
                    return(false);
                }

                //Move units from attackSolution to targetSolution
                var removedSolution = EntitySystem.Get <SolutionContainerSystem>()
                                      .Drain(itemEntity, attackSolution, realTransferAmount);
                if (!EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(Owner, solution, removedSolution))
                {
                    return(false);
                }

                Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-transfer-success",
                                                                 ("amount", removedSolution.TotalVolume)));
                return(true);
            }

            if (!_entities.TryGetComponent(itemEntity, typeof(ItemComponent), out var food))
            {
                Owner.PopupMessage(eventArgs.User, "microwave-component-interact-using-transfer-fail");
                return(false);
            }

            var ent = food.Owner; //Get the entity of the ItemComponent.

            _storage.Insert(ent);
            _uiDirty = true;
            return(true);
        }
    public PuddleComponent?SpillAt(TileRef tileRef, Solution solution, string prototype,
                                   bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true)
    {
        if (solution.TotalVolume <= 0)
        {
            return(null);
        }

        // If space return early, let that spill go out into the void
        if (tileRef.Tile.IsEmpty)
        {
            return(null);
        }

        var gridId = tileRef.GridIndex;

        if (!_mapManager.TryGetGrid(gridId, out var mapGrid))
        {
            return(null);                                                  // Let's not spill to invalid grids.
        }
        if (!noTileReact)
        {
            // First, do all tile reactions
            foreach (var(reagentId, quantity) in solution.Contents)
            {
                var proto = _prototypeManager.Index <ReagentPrototype>(reagentId);
                proto.ReactionTile(tileRef, quantity);
            }
        }

        // Tile reactions used up everything.
        if (solution.CurrentVolume == FixedPoint2.Zero)
        {
            return(null);
        }

        // Get normalized co-ordinate for spill location and spill it in the centre
        // TODO: Does SnapGrid or something else already do this?
        var spillGridCoords = mapGrid.GridTileToWorld(tileRef.GridIndices);

        var spillEntities = _entityLookup.GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray();

        foreach (var spillEntity in spillEntities)
        {
            if (_solutionContainerSystem.TryGetRefillableSolution(spillEntity, out var solutionContainerComponent))
            {
                _solutionContainerSystem.Refill(spillEntity, solutionContainerComponent,
                                                solution.SplitSolution(FixedPoint2.Min(
                                                                           solutionContainerComponent.AvailableVolume,
                                                                           solutionContainerComponent.MaxSpillRefill))
                                                );
            }
        }

        if (combine)
        {
            foreach (var spillEntity in spillEntities)
            {
                if (!EntityManager.TryGetComponent(spillEntity, out PuddleComponent? puddleComponent))
                {
                    continue;
                }

                if (!overflow && _puddleSystem.WouldOverflow(puddleComponent.Owner, solution, puddleComponent))
                {
                    return(null);
                }

                if (!_puddleSystem.TryAddSolution(puddleComponent.Owner, solution, sound))
                {
                    continue;
                }

                return(puddleComponent);
            }
        }

        var puddleEnt          = EntityManager.SpawnEntity(prototype, spillGridCoords);
        var newPuddleComponent = EntityManager.GetComponent <PuddleComponent>(puddleEnt);

        _puddleSystem.TryAddSolution(newPuddleComponent.Owner, solution, sound);

        return(newPuddleComponent);
    }
        /// <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);
        }
        async Task <bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
        {
            var solutionsSys = EntitySystem.Get <SolutionContainerSystem>();

            if (!eventArgs.InRangeUnobstructed() || eventArgs.Target == null)
            {
                return(false);
            }

            if (!_entities.HasComponent <SolutionContainerManagerComponent>(Owner))
            {
                return(false);
            }

            var target = eventArgs.Target !.Value;

            if (!_entities.HasComponent <SolutionContainerManagerComponent>(target))
            {
                return(false);
            }


            if (CanReceive && _entities.TryGetComponent(target, out ReagentTankComponent? tank) &&
                solutionsSys.TryGetRefillableSolution(Owner, out var ownerRefill) &&
                solutionsSys.TryGetDrainableSolution(target, out var targetDrain))
            {
                var tankTransferAmount = tank.TransferAmount;

                if (_entities.TryGetComponent(Owner, out RefillableSolutionComponent? refill) && refill.MaxRefill != null)
                {
                    tankTransferAmount = FixedPoint2.Min(tankTransferAmount, (FixedPoint2)refill.MaxRefill);
                }

                var transferred = DoTransfer(eventArgs.User, target, targetDrain, Owner, ownerRefill, tankTransferAmount);
                if (transferred > 0)
                {
                    var toTheBrim = ownerRefill.AvailableVolume == 0;
                    var msg       = toTheBrim
                        ? "comp-solution-transfer-fill-fully"
                        : "comp-solution-transfer-fill-normal";

                    target.PopupMessage(eventArgs.User,
                                        Loc.GetString(msg, ("owner", eventArgs.Target), ("amount", transferred), ("target", Owner)));
                    return(true);
                }
            }

            if (CanSend && solutionsSys.TryGetRefillableSolution(target, out var targetRefill) &&
                solutionsSys.TryGetDrainableSolution(Owner, out var ownerDrain))
            {
                var transferAmount = TransferAmount;

                if (_entities.TryGetComponent(target, out RefillableSolutionComponent? refill) && refill.MaxRefill != null)
                {
                    transferAmount = FixedPoint2.Min(transferAmount, (FixedPoint2)refill.MaxRefill);
                }

                var transferred = DoTransfer(eventArgs.User, Owner, ownerDrain, target, targetRefill, transferAmount);

                if (transferred > 0)
                {
                    Owner.PopupMessage(eventArgs.User,
                                       Loc.GetString("comp-solution-transfer-transfer-solution",
                                                     ("amount", transferred),
                                                     ("target", target)));

                    return(true);
                }
            }

            return(true);
        }
    // 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);
            }
        }
Beispiel #24
0
        /// <summary>
        ///     Attempt to drink some of a drink. Returns true if any interaction took place, including generation of
        ///     pop-up messages.
        /// </summary>
        private bool TryUseDrink(EntityUid uid, EntityUid userUid, DrinkComponent?drink = null)
        {
            if (!Resolve(uid, ref drink))
            {
                return(false);
            }

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

            if (!drink.Opened)
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open",
                                                       ("owner", Name: EntityManager.GetComponent <MetaDataComponent>(drink.Owner).EntityName)), uid, Filter.Entities(userUid));
                return(true);
            }

            if (!EntityManager.TryGetComponent(userUid, out SharedBodyComponent? body))
            {
                return(false);
            }

            if (!_solutionContainerSystem.TryGetDrainableSolution((drink).Owner, out var drinkSolution) ||
                drinkSolution.DrainAvailable <= 0)
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty",
                                                       ("entity", Name: EntityManager.GetComponent <MetaDataComponent>(drink.Owner).EntityName)), uid, Filter.Entities(userUid));
                return(true);
            }

            if (!_bodySystem.TryGetComponentsOnMechanisms <StomachComponent>(userUid, out var stomachs, body))
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-cannot-drink"),
                                         userUid, Filter.Entities(userUid));
                return(true);
            }

            if (_foodSystem.IsMouthBlocked(userUid, userUid))
            {
                return(true);
            }

            var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable);
            var drain          = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount);
            var firstStomach   = stomachs.FirstOrNull(
                stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, drain));

            // All stomach are full or can't handle whatever solution we have.
            if (firstStomach == null)
            {
                _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"),
                                         userUid, Filter.Entities(userUid));

                if (EntityManager.HasComponent <RefillableSolutionComponent>(uid))
                {
                    _spillableSystem.SpillAt(userUid, drain, "PuddleSmear");
                    return(true);
                }

                _solutionContainerSystem.Refill(uid, drinkSolution, drain);
                return(true);
            }

            SoundSystem.Play(Filter.Pvs(userUid), drink.UseSound.GetSound(), userUid,
                             AudioParams.Default.WithVolume(-2f));

            _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-success-slurp"), userUid,
                                     Filter.Pvs(userUid));

            drain.DoEntityReaction(userUid, ReactionMethod.Ingestion);
            _stomachSystem.TryTransferSolution((firstStomach.Value.Comp).Owner, drain, firstStomach.Value.Comp);

            return(true);
        }
        private void TransferReagent(string id, FixedPoint2 amount, bool isBuffer)
        {
            if (!BeakerSlot.HasItem && _bufferModeTransfer)
            {
                return;
            }

            if (BeakerSlot.Item is not {
                Valid: true
            } beaker ||
                !_entities.TryGetComponent(beaker, out FitsInDispenserComponent? fits) ||
                !EntitySystem.Get <SolutionContainerSystem>().TryGetSolution(beaker, fits.Solution, out var beakerSolution))
            {
                return;
            }

            if (isBuffer)
            {
                foreach (var reagent in BufferSolution.Contents)
                {
                    if (reagent.ReagentId == id)
                    {
                        FixedPoint2 actualAmount;
                        if (
                            amount == FixedPoint2
                            .New(-1))     //amount is FixedPoint2.New(-1) when the client sends a message requesting to remove all solution from the container
                        {
                            actualAmount = FixedPoint2.Min(reagent.Quantity, beakerSolution.AvailableVolume);
                        }
                        else
                        {
                            actualAmount = FixedPoint2.Min(reagent.Quantity, amount, beakerSolution.AvailableVolume);
                        }


                        BufferSolution.RemoveReagent(id, actualAmount);
                        if (_bufferModeTransfer)
                        {
                            EntitySystem.Get <SolutionContainerSystem>()
                            .TryAddReagent(beaker, beakerSolution, id, actualAmount, out var _);
                            // beakerSolution.Solution.AddReagent(id, actualAmount);
                        }

                        break;
                    }
                }
            }
            else
            {
                foreach (var reagent in beakerSolution.Contents)
                {
                    if (reagent.ReagentId == id)
                    {
                        FixedPoint2 actualAmount;
                        if (amount == FixedPoint2.New(-1))
                        {
                            actualAmount = reagent.Quantity;
                        }
                        else
                        {
                            actualAmount = FixedPoint2.Min(reagent.Quantity, amount);
                        }

                        EntitySystem.Get <SolutionContainerSystem>().TryRemoveReagent(beaker, beakerSolution, id, actualAmount);
                        BufferSolution.AddReagent(id, actualAmount);
                        break;
                    }
                }
            }
            _label = GenerateLabel();
            UpdateUserInterface();
        }
        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);
        private void TryCreatePackage(EntityUid user, UiAction action, string label, int pillAmount, int bottleAmount)
        {
            if (BufferSolution.TotalVolume == 0)
            {
                user.PopupMessageCursor(Loc.GetString("chem-master-window-buffer-empty-text"));
                return;
            }

            if (action == UiAction.CreateBottles)
            {
                var individualVolume = BufferSolution.TotalVolume / FixedPoint2.New(bottleAmount);
                if (individualVolume < FixedPoint2.New(1))
                {
                    user.PopupMessageCursor(Loc.GetString("chem-master-window-buffer-low-text"));
                    return;
                }

                var actualVolume = FixedPoint2.Min(individualVolume, FixedPoint2.New(30));
                for (int i = 0; i < bottleAmount; i++)
                {
                    var bottle = _entities.SpawnEntity("ChemistryEmptyBottle01", _entities.GetComponent <TransformComponent>(Owner).Coordinates);

                    //Adding label
                    LabelComponent labelComponent = bottle.EnsureComponent <LabelComponent>();
                    labelComponent.OriginalName = _entities.GetComponent <MetaDataComponent>(bottle).EntityName;
                    string val = _entities.GetComponent <MetaDataComponent>(bottle).EntityName + $" ({label})";
                    _entities.GetComponent <MetaDataComponent>(bottle).EntityName = val;
                    labelComponent.CurrentLabel = label;

                    var bufferSolution = BufferSolution.SplitSolution(actualVolume);
                    var bottleSolution = EntitySystem.Get <SolutionContainerSystem>().EnsureSolution(bottle, "drink");

                    EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(bottle, bottleSolution, bufferSolution);

                    //Try to give them the bottle
                    if (_entities.TryGetComponent <HandsComponent?>(user, out var hands) &&
                        _entities.TryGetComponent <SharedItemComponent?>(bottle, out var item))
                    {
                        if (hands.CanPutInHand(item))
                        {
                            hands.PutInHand(item);
                            continue;
                        }
                    }

                    //Put it on the floor
                    _entities.GetComponent <TransformComponent>(bottle).Coordinates = _entities.GetComponent <TransformComponent>(user).Coordinates;
                    //Give it an offset
                    bottle.RandomOffset(0.2f);
                }
            }
            else //Pills
            {
                var individualVolume = BufferSolution.TotalVolume / FixedPoint2.New(pillAmount);
                if (individualVolume < FixedPoint2.New(1))
                {
                    user.PopupMessageCursor(Loc.GetString("chem-master-window-buffer-low-text"));
                    return;
                }

                var actualVolume = FixedPoint2.Min(individualVolume, FixedPoint2.New(50));
                for (int i = 0; i < pillAmount; i++)
                {
                    var pill = _entities.SpawnEntity("pill", _entities.GetComponent <TransformComponent>(Owner).Coordinates);

                    //Adding label
                    LabelComponent labelComponent = pill.EnsureComponent <LabelComponent>();
                    labelComponent.OriginalName = _entities.GetComponent <MetaDataComponent>(pill).EntityName;
                    string val = _entities.GetComponent <MetaDataComponent>(pill).EntityName + $" ({label})";
                    _entities.GetComponent <MetaDataComponent>(pill).EntityName = val;
                    labelComponent.CurrentLabel = label;

                    var bufferSolution = BufferSolution.SplitSolution(actualVolume);
                    var pillSolution   = EntitySystem.Get <SolutionContainerSystem>().EnsureSolution(pill, "food");
                    EntitySystem.Get <SolutionContainerSystem>().TryAddSolution(pill, pillSolution, bufferSolution);

                    //Change pill Sprite component state
                    if (!_entities.TryGetComponent(pill, out SpriteComponent? sprite))
                    {
                        return;
                    }
                    sprite?.LayerSetState(0, "pill" + _pillType);

                    //Try to give them the bottle
                    if (_entities.TryGetComponent <HandsComponent?>(user, out var hands) &&
                        _entities.TryGetComponent <SharedItemComponent?>(pill, out var item))
                    {
                        if (hands.CanPutInHand(item))
                        {
                            hands.PutInHand(item);
                            continue;
                        }
                    }

                    //Put it on the floor
                    _entities.GetComponent <TransformComponent>(pill).Coordinates = _entities.GetComponent <TransformComponent>(user).Coordinates;
                    //Give it an offset
                    pill.RandomOffset(0.2f);
                }
            }

            if (_bufferSolution?.Contents.Count == 0)
            {
                _label = "";
            }

            UpdateUserInterface();
        }
        async Task <bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
        {
            if (!eventArgs.CanReach || eventArgs.Target == null)
            {
                return(false);
            }

            var target         = eventArgs.Target !.Value;
            var solutionsSys   = EntitySystem.Get <SolutionContainerSystem>();
            var transferSystem = EntitySystem.Get <SolutionTransferSystem>();

            //Special case for reagent tanks, because normally clicking another container will give solution, not take it.
            if (CanReceive && !_entities.HasComponent <RefillableSolutionComponent>(target) && // target must not be refillable (e.g. Reagent Tanks)
                solutionsSys.TryGetDrainableSolution(target, out var targetDrain) &&             // target must be drainable
                _entities.TryGetComponent(Owner, out RefillableSolutionComponent? refillComp) &&
                solutionsSys.TryGetRefillableSolution(Owner, out var ownerRefill, refillable: refillComp))

            {
                var transferAmount = TransferAmount;                                                                       // This is the player-configurable transfer amount of "Owner," not the target reagent tank.

                if (_entities.TryGetComponent(Owner, out RefillableSolutionComponent? refill) && refill.MaxRefill != null) // Owner is the entity receiving solution from target.
                {
                    transferAmount = FixedPoint2.Min(transferAmount, (FixedPoint2)refill.MaxRefill);                       // if the receiver has a smaller transfer limit, use that instead
                }

                var transferred = transferSystem.Transfer(eventArgs.User, target, targetDrain, Owner, ownerRefill, transferAmount);
                if (transferred > 0)
                {
                    var toTheBrim = ownerRefill.AvailableVolume == 0;
                    var msg       = toTheBrim
                        ? "comp-solution-transfer-fill-fully"
                        : "comp-solution-transfer-fill-normal";

                    target.PopupMessage(eventArgs.User,
                                        Loc.GetString(msg, ("owner", eventArgs.Target), ("amount", transferred), ("target", Owner)));
                    return(true);
                }
            }

            // if target is refillable, and owner is drainable
            if (CanSend && solutionsSys.TryGetRefillableSolution(target, out var targetRefill) &&
                solutionsSys.TryGetDrainableSolution(Owner, out var ownerDrain))
            {
                var transferAmount = TransferAmount;

                if (_entities.TryGetComponent(target, out RefillableSolutionComponent? refill) && refill.MaxRefill != null)
                {
                    transferAmount = FixedPoint2.Min(transferAmount, (FixedPoint2)refill.MaxRefill);
                }

                var transferred = transferSystem.Transfer(eventArgs.User, Owner, ownerDrain, target, targetRefill, transferAmount);

                if (transferred > 0)
                {
                    Owner.PopupMessage(eventArgs.User,
                                       Loc.GetString("comp-solution-transfer-transfer-solution",
                                                     ("amount", transferred),
                                                     ("target", target)));

                    return(true);
                }
            }

            return(false);
        }