using Content.Server.Administration.Logs; using Content.Server.Fluids.EntitySystems; using Content.Server.Ghost; using Content.Server.Popups; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.Destructible; using Content.Shared.Emag.Components; using Content.Shared.Gibbing; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; using Content.Shared.Materials; using Content.Shared.Mind; using Content.Shared.Power; using Content.Shared.Repairable; using Content.Shared.Stacks; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Server.Materials; /// public sealed partial class MaterialReclaimerSystem : SharedMaterialReclaimerSystem { [Dependency] private AppearanceSystem _appearance = default!; [Dependency] private GhostSystem _ghost = default!; [Dependency] private MaterialStorageSystem _materialStorage = default!; [Dependency] private PopupSystem _popup = default!; [Dependency] private SharedSolutionContainerSystem _solutionContainer = default!; [Dependency] private GibbingSystem _gibbing = default!; [Dependency] private PuddleSystem _puddle = default!; [Dependency] private SharedMindSystem _mind = default!; [Dependency] private IAdminLogManager _adminLogger = default!; [Dependency] private SharedDestructibleSystem _destructible = default!; [Dependency] private DamageableSystem _damage = default!; [SubscribeLocalEvent] private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { AmbientSound.SetAmbience(entity.Owner, entity.Comp.Enabled && args.Powered); entity.Comp.Powered = args.Powered; Dirty(entity); } [SubscribeLocalEvent] private void OnSuicideByEnvironment(Entity entity, ref SuicideByEnvironmentEvent args) { if (args.Handled) return; var victim = args.Victim; if (TryComp(victim, out ActorComponent? actor) && _mind.TryGetMind(actor.PlayerSession, out var mindId, out var mind)) { _ghost.OnGhostAttempt(mindId, false, mind: mind); if (mind.OwnedEntity is { Valid: true } suicider) { _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message"), suicider); } } _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message-others", ("victim", Identity.Entity(victim, EntityManager))), victim, Filter.PvsExcept(victim, entityManager: EntityManager), true); _gibbing.Gib(victim); _appearance.SetData(entity.Owner, RecyclerVisuals.Bloody, true); args.Handled = true; } [SubscribeLocalEvent] private void OnActivePowerChanged(Entity entity, ref PowerChangedEvent args) { if (!args.Powered) TryFinishProcessItem(entity, null, entity.Comp); } [SubscribeLocalEvent] private void OnBreakage(Entity ent, ref BreakageEventArgs args) { //un-emags itself when it breaks RemComp(ent); SetBroken(ent, true); } [SubscribeLocalEvent] private void OnRepaired(Entity ent, ref RepairedEvent args) { SetBroken(ent, false); } public void SetBroken(Entity ent, bool val) { if (ent.Comp.Broken == val) return; _appearance.SetData(ent, RecyclerVisuals.Broken, val); SetReclaimerEnabled(ent, false); ent.Comp.Broken = val; Dirty(ent); } /// 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; Container.Remove(item, active.ReclaimingContainer); Dirty(uid, 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); if (component.ReclaimMaterials) SpawnMaterialsFromComposition(uid, item, completion * component.Efficiency, xform: xform); if (CanDamageAndGib(uid, item, component)) { var didBloody = component.DamageOnEmag is not null && _damage.TryChangeDamage(item, component.DamageOnEmag); // It shouldn't ignore resistance if (_destructible.CanDestroy(item) && component.GibOnEmag) { var logImpact = HasComp(item) ? LogImpact.Extreme : LogImpact.Medium; _adminLogger.Add(LogType.Gib, logImpact, $"{ToPrettyString(item):victim} was gibbed by {ToPrettyString(uid):entity}"); _gibbing.Gib(item); didBloody = true; } if (didBloody) _appearance.SetData(uid, RecyclerVisuals.Bloody, true); return; } if (_destructible.CanDestroy(item) && component.ReclaimSolutions) SpawnChemicalsFromComposition(uid, item, completion, true, component, xform); _destructible.DestroyEntity(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; // If more of these checks are needed, use an event instead var modifier = CompOrNull(item)?.Count ?? 1.0f; foreach (var (material, amount) in composition.MaterialComposition) { var outputAmount = (int)(amount * efficiency * modifier); _materialStorage.TryChangeMaterialAmount(reclaimer, material, outputAmount, storage); } _materialStorage.EjectAllMaterial(reclaimer, xform.Coordinates, storage, true); } private void SpawnChemicalsFromComposition(EntityUid reclaimer, EntityUid item, float efficiency, bool sound = true, MaterialReclaimerComponent? reclaimerComponent = null, TransformComponent? xform = null, PhysicalCompositionComponent? composition = null) { if (!Resolve(reclaimer, ref reclaimerComponent, ref xform)) return; efficiency *= reclaimerComponent.Efficiency; // Solution will be empty, nothing to do. if (efficiency <= 0) return; var totalChemicals = new Solution(); if (Resolve(item, ref composition, false)) { foreach (var (key, value) in composition.ChemicalComposition) { // TODO use ReagentQuantity totalChemicals.AddReagent(key, value * efficiency, false); } } // if the item we inserted has reagents, add it in. if (reclaimerComponent.OnlyReclaimDrainable) { // Are we a recycler? Only use drainable solution. if (_solutionContainer.TryGetDrainableSolution(item, out _, out var drainableSolution)) { totalChemicals.AddSolution(drainableSolution, ProtoMan); } } else { // Are we an industrial reagent grinder? Use extractable solution. if (_solutionContainer.TryGetExtractableSolution(item, out _, out var extractableSolution)) { totalChemicals.AddSolution(extractableSolution, ProtoMan); } } // Transfer or spill the solution if there's anything to move. if (totalChemicals.Volume <= 0) return; if (reclaimerComponent.SolutionContainerId == null || !_solutionContainer.TryGetSolution(reclaimer, reclaimerComponent.SolutionContainerId, out var outputSolution) || !_solutionContainer.TryTransferSolution(outputSolution.Value, totalChemicals, totalChemicals.Volume)) { _puddle.TrySpillAt(reclaimer, totalChemicals, out _, sound, transformComponent: xform); } } }