using Content.Shared.Body.Components; using Content.Shared.Body.Events; using Content.Shared.Body.Organ; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Shared.Body.Systems { public sealed class StomachSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; public const string DefaultSolutionName = "stomach"; public override void Initialize() { SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnEntRemoved); SubscribeLocalEvent(OnApplyMetabolicMultiplier); } private void OnMapInit(Entity ent, ref MapInitEvent args) { ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; } private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args) { ent.Comp.NextUpdate += args.PausedTime; } private void OnEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) { // Make sure the removed entity was our contained solution if (ent.Comp.Solution is not { } solution || args.Entity != solution.Owner) return; // Cleared our cached reference to the solution entity ent.Comp.Solution = null; } public override void Update(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var stomach, out var organ, out var sol)) { if (_gameTiming.CurTime < stomach.NextUpdate) continue; stomach.NextUpdate += stomach.AdjustedUpdateInterval; // Get our solutions if (!_solutionContainerSystem.ResolveSolution((uid, sol), DefaultSolutionName, ref stomach.Solution, out var stomachSolution)) continue; if (organ.Body is not { } body || !_solutionContainerSystem.TryGetSolution(body, stomach.BodySolutionName, out var bodySolution)) continue; var transferSolution = new Solution(); var queue = new RemQueue(); foreach (var delta in stomach.ReagentDeltas) { delta.Increment(stomach.AdjustedUpdateInterval); if (delta.Lifetime > stomach.DigestionDelay) { if (stomachSolution.TryGetReagent(delta.ReagentQuantity.Reagent, out var reagent)) { if (reagent.Quantity > delta.ReagentQuantity.Quantity) reagent = new(reagent.Reagent, delta.ReagentQuantity.Quantity); stomachSolution.RemoveReagent(reagent); transferSolution.AddReagent(reagent); } queue.Add(delta); } } foreach (var item in queue) { stomach.ReagentDeltas.Remove(item); } _solutionContainerSystem.UpdateChemicals(stomach.Solution.Value); // Transfer everything to the body solution! _solutionContainerSystem.TryAddSolution(bodySolution.Value, transferSolution); } } private void OnApplyMetabolicMultiplier(Entity ent, ref ApplyMetabolicMultiplierEvent args) { ent.Comp.UpdateIntervalMultiplier = args.Multiplier; } public bool CanTransferSolution( EntityUid uid, Solution solution, StomachComponent? stomach = null, SolutionContainerManagerComponent? solutions = null) { return Resolve(uid, ref stomach, ref solutions, logMissing: false) && _solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution) // TODO: For now no partial transfers. Potentially change by design && stomachSolution.CanAddSolution(solution); } public bool TryTransferSolution( EntityUid uid, Solution solution, StomachComponent? stomach = null, SolutionContainerManagerComponent? solutions = null) { if (!Resolve(uid, ref stomach, ref solutions, logMissing: false) || !_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution) || !CanTransferSolution(uid, solution, stomach, solutions)) { return false; } _solutionContainerSystem.TryAddSolution(stomach.Solution.Value, solution); // Add each reagent to ReagentDeltas. Used to track how long each reagent has been in the stomach foreach (var reagent in solution.Contents) { stomach.ReagentDeltas.Add(new StomachComponent.ReagentDelta(reagent)); } return true; } } }