using System.Linq; using Content.Server.Administration.Logs; using Content.Shared.Materials; using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Server.Power.Components; using Content.Server.Construction.Components; using Content.Server.Stack; using Content.Shared.Database; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Server.Materials; /// /// This handles /// public sealed class MaterialStorageSystem : SharedMaterialStorageSystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly StackSystem _stackSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDeconstructed); } private void OnDeconstructed(EntityUid uid, MaterialStorageComponent component, MachineDeconstructedEvent args) { if (!component.DropOnDeconstruct) return; foreach (var (material, amount) in component.Storage) { SpawnMultipleFromMaterial(amount, material, Transform(uid).Coordinates); } } public override bool TryInsertMaterialEntity(EntityUid user, EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? storage = null, MaterialComponent? material = null, PhysicalCompositionComponent? composition = null) { if (!Resolve(receiver, ref storage) || !Resolve(toInsert, ref material, ref composition, false)) return false; if (TryComp(receiver, out var power) && !power.Powered) return false; if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition)) return false; _audio.PlayPvs(storage.InsertingSound, receiver); _popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver), ("item", toInsert)), receiver); QueueDel(toInsert); // Logging TryComp(toInsert, out var stack); var count = stack?.Count ?? 1; _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} inserted {count} {ToPrettyString(toInsert):inserted} into {ToPrettyString(receiver):receiver}"); return true; } /// /// Spawn an amount of a material in stack entities. /// Note the 'amount' is material dependent. /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// public List SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates) { return SpawnMultipleFromMaterial(amount, material, coordinates, out _); } /// /// Spawn an amount of a material in stack entities. /// Note the 'amount' is material dependent. /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// public List SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates, out int overflowMaterial) { overflowMaterial = 0; if (!_prototypeManager.TryIndex(material, out var stackType)) { Logger.Error("Failed to index material prototype " + material); return new List(); } return SpawnMultipleFromMaterial(amount, stackType, coordinates, out overflowMaterial); } /// /// Spawn an amount of a material in stack entities. /// Note the 'amount' is material dependent. /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// [PublicAPI] public List SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates) { return SpawnMultipleFromMaterial(amount, materialProto, coordinates, out _); } /// /// Spawn an amount of a material in stack entities. /// Note the 'amount' is material dependent. /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// public List SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates, out int overflowMaterial) { overflowMaterial = 0; if (amount <= 0 || materialProto.StackEntity == null) return new List(); var entProto = _prototypeManager.Index(materialProto.StackEntity); if (!entProto.TryGetComponent(out var composition)) return new List(); var materialPerStack = composition.MaterialComposition[materialProto.ID]; var amountToSpawn = amount / materialPerStack; overflowMaterial = amount - amountToSpawn * materialPerStack; return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates); } /// /// Eject a material out of this storage. The internal counts are updated. /// Material that cannot be ejected stays in storage. (e.g. only have 50 but a sheet needs 100). /// /// The entity with storage to eject from. /// The material prototype to eject. /// The maximum amount to eject. If not given, as much as possible is ejected. /// The position where to spawn the created sheets. If not given, they're spawned next to the entity. /// The storage component on . Resolved automatically if not given. /// The stack entities that were spawned. public List EjectMaterial( EntityUid entity, string material, int? maxAmount = null, EntityCoordinates? coordinates = null, MaterialStorageComponent? component = null) { if (!Resolve(entity, ref component)) return new List(); coordinates ??= Transform(entity).Coordinates; var amount = GetMaterialAmount(entity, material, component); if (maxAmount != null) amount = Math.Min(maxAmount.Value, amount); var spawned = SpawnMultipleFromMaterial(amount, material, coordinates.Value, out var overflow); TryChangeMaterialAmount(entity, material, -(amount - overflow), component); return spawned; } /// /// Eject all material stored in an entity, with the same mechanics as . /// /// The entity with storage to eject from. /// The position where to spawn the created sheets. If not given, they're spawned next to the entity. /// The storage component on . Resolved automatically if not given. /// The stack entities that were spawned. public List EjectAllMaterial( EntityUid entity, EntityCoordinates? coordinates = null, MaterialStorageComponent? component = null) { if (!Resolve(entity, ref component)) return new List(); coordinates ??= Transform(entity).Coordinates; var allSpawned = new List(); foreach (var material in component.Storage.Keys.ToArray()) { var spawned = EjectMaterial(entity, material, null, coordinates, component); allSpawned.AddRange(spawned); } return allSpawned; } }