using System.Linq; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.Construction; using Content.Server.Fluids.EntitySystems; using Content.Server.GameTicking; using Content.Server.Mind; using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Stack; using Content.Server.Wires; using Content.Shared.Body.Systems; using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Materials; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Server.Materials; /// public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem { [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly MaterialStorageSystem _materialStorage = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly SharedBodySystem _body = default!; //bobby [Dependency] private readonly PuddleSystem _puddle = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly MindSystem _mind = default!; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnRefreshParts); SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnInteractUsing, before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)}); SubscribeLocalEvent(OnSuicide); SubscribeLocalEvent(OnActivePowerChanged); } private void OnStartup(EntityUid uid, MaterialReclaimerComponent component, ComponentStartup args) { component.OutputSolution = _solutionContainer.EnsureSolution(uid, component.SolutionContainerId); } private void OnUpgradeExamine(EntityUid uid, MaterialReclaimerComponent component, UpgradeExamineEvent args) { args.AddPercentageUpgrade(Loc.GetString("material-reclaimer-upgrade-process-rate"), component.MaterialProcessRate / component.BaseMaterialProcessRate); } private void OnRefreshParts(EntityUid uid, MaterialReclaimerComponent component, RefreshPartsEvent args) { var rating = args.PartRatings[component.MachinePartProcessRate] - 1; component.MaterialProcessRate = component.BaseMaterialProcessRate * MathF.Pow(component.PartRatingProcessRateMultiplier, rating); Dirty(component); } private void OnPowerChanged(EntityUid uid, MaterialReclaimerComponent component, ref PowerChangedEvent args) { AmbientSound.SetAmbience(uid, component.Enabled && args.Powered); component.Powered = args.Powered; Dirty(component); } private void OnInteractUsing(EntityUid uid, MaterialReclaimerComponent component, InteractUsingEvent args) { if (args.Handled) return; // if we're trying to get a solution out of the reclaimer, don't destroy it if (component.OutputSolution.Contents.Any()) { if (TryComp(args.Used, out var managerComponent) && managerComponent.Solutions.Any(s => s.Value.AvailableVolume > 0)) { if (TryComp(args.Used, out var drink) && !drink.Opened) return; if (TryComp(args.Used, out var transfer) && transfer.CanReceive) return; } } args.Handled = TryStartProcessItem(uid, args.Used, component, args.User); } private void OnSuicide(EntityUid uid, MaterialReclaimerComponent component, SuicideEvent args) { if (args.Handled) return; args.SetHandled(SuicideKind.Bloodloss); var victim = args.Victim; if (TryComp(victim, out ActorComponent? actor) && _mind.TryGetMind(actor.PlayerSession, out var mindId, out var mind)) { _ticker.OnGhostAttempt(mindId, false, mind: mind); if (mind.OwnedEntity is { Valid: true } entity) { _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message"), entity); } } _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message-others", ("victim", Identity.Entity(victim, EntityManager))), victim, Filter.PvsExcept(victim, entityManager: EntityManager), true); _body.GibBody(victim, true); _appearance.SetData(uid, RecyclerVisuals.Bloody, true); } private void OnActivePowerChanged(EntityUid uid, ActiveMaterialReclaimerComponent component, ref PowerChangedEvent args) { if (!args.Powered) TryFinishProcessItem(uid, null, component); } /// public override bool TryFinishProcessItem(EntityUid uid, MaterialReclaimerComponent? component = null, ActiveMaterialReclaimerComponent? active = null) { if (!Resolve(uid, ref component, ref active, false)) return false; if (!base.TryFinishProcessItem(uid, component, active)) return false; if (active.ReclaimingContainer.ContainedEntities.FirstOrNull() is not { } item) return false; active.ReclaimingContainer.Remove(item); Dirty(component); // scales the output if the process was interrupted. var completion = 1f - Math.Clamp((float) Math.Round((active.EndTime - Timing.CurTime) / active.Duration), 0f, 1f); Reclaim(uid, item, completion, component); return true; } /// public override void Reclaim(EntityUid uid, EntityUid item, float completion = 1f, MaterialReclaimerComponent? component = null) { if (!Resolve(uid, ref component)) return; base.Reclaim(uid, item, completion, component); var xform = Transform(uid); SpawnMaterialsFromComposition(uid, item, completion * component.Efficiency, xform: xform); SpawnChemicalsFromComposition(uid, item, completion, component, xform); if (CanGib(uid, item, component)) { _body.GibBody(item, true); _appearance.SetData(uid, RecyclerVisuals.Bloody, true); } QueueDel(item); } private void SpawnMaterialsFromComposition(EntityUid reclaimer, EntityUid item, float efficiency, MaterialStorageComponent? storage = null, TransformComponent? xform = null, PhysicalCompositionComponent? composition = null) { if (!Resolve(reclaimer, ref storage, ref xform, false)) return; if (!Resolve(item, ref composition, false)) return; foreach (var (material, amount) in composition.MaterialComposition) { var outputAmount = (int) (amount * efficiency); _materialStorage.TryChangeMaterialAmount(reclaimer, material, outputAmount, storage); } foreach (var (storedMaterial, storedAmount) in storage.Storage) { var stacks = _materialStorage.SpawnMultipleFromMaterial(storedAmount, storedMaterial, xform.Coordinates, out var materialOverflow); var amountConsumed = storedAmount - materialOverflow; _materialStorage.TryChangeMaterialAmount(reclaimer, storedMaterial, -amountConsumed, storage); foreach (var stack in stacks) { _stack.TryMergeToContacts(stack); } } } private void SpawnChemicalsFromComposition(EntityUid reclaimer, EntityUid item, float efficiency, MaterialReclaimerComponent? reclaimerComponent = null, TransformComponent? xform = null, PhysicalCompositionComponent? composition = null) { if (!Resolve(reclaimer, ref reclaimerComponent, ref xform)) return; var overflow = new Solution(); var totalChemicals = new Dictionary(); if (Resolve(item, ref composition, false)) { foreach (var (key, value) in composition.ChemicalComposition) { totalChemicals[key] = totalChemicals.GetValueOrDefault(key) + value; } } // if the item we inserted has reagents, add it in. if (TryComp(item, out var solutionContainer)) { foreach (var solution in solutionContainer.Solutions.Values) { foreach (var quantity in solution.Contents) { totalChemicals[quantity.ReagentId] = totalChemicals.GetValueOrDefault(quantity.ReagentId) + quantity.Quantity; } } } foreach (var (reagent, amount) in totalChemicals) { var outputAmount = amount * efficiency * reclaimerComponent.Efficiency; _solutionContainer.TryAddReagent(reclaimer, reclaimerComponent.OutputSolution, reagent, outputAmount, out var accepted); var overflowAmount = outputAmount - accepted; if (overflowAmount > 0) { overflow.AddReagent(reagent, overflowAmount); } } if (overflow.Volume > 0) { _puddle.TrySpillAt(reclaimer, overflow, out _, transformComponent: xform); } } }