using Content.Shared.Administration.Logs; using Content.Shared.Body; using Content.Shared.Cloning; using Content.Shared.Cloning.Events; using Content.Shared.Database; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Inventory; using Content.Shared.Implants; using Content.Shared.Implants.Components; using Content.Shared.NameModifier.EntitySystems; using Content.Shared.StatusEffect; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Content.Server.Cloning; /// /// System responsible for making a copy of a humanoid's body. /// For the cloning machines themselves look at CloningPodSystem, CloningConsoleSystem and MedicalScannerSystem instead. /// public sealed partial class CloningSystem : SharedCloningSystem { [Dependency] private InventorySystem _inventory = default!; [Dependency] private MetaDataSystem _metaData = default!; [Dependency] private EntityWhitelistSystem _whitelist = default!; [Dependency] private ISharedAdminLogManager _adminLogger = default!; [Dependency] private SharedContainerSystem _container = default!; [Dependency] private SharedStorageSystem _storage = default!; [Dependency] private SharedSubdermalImplantSystem _subdermalImplant = default!; [Dependency] private SharedVisualBodySystem _visualBody = default!; [Dependency] private NameModifierSystem _nameMod = default!; [Dependency] private IdentitySystem _identity = default!; public override bool TryCloning( EntityUid original, MapCoordinates? coords, ProtoId settingsId, [NotNullWhen(true)] out EntityUid? clone) { clone = null; if (!ProtoMan.Resolve(settingsId, out var settings)) return false; // invalid settings if (!TryComp(original, out var humanoid)) return false; // whatever body was to be cloned, was not a humanoid if (!ProtoMan.Resolve(humanoid.Species, out var speciesPrototype)) return false; // invalid species if (!settings.ForceCloning) { var attemptEv = new CloningAttemptEvent(settings); RaiseLocalEvent(original, ref attemptEv); if (attemptEv.Cancelled) return false; // cannot clone, for example due to the unrevivable trait } clone = coords == null ? Spawn(speciesPrototype.Prototype) : Spawn(speciesPrototype.Prototype, coords.Value); _visualBody.CopyAppearanceFrom(original, clone.Value); CloneComponents(original, clone.Value, settings); // Add equipment first so that SetEntityName also renames the ID card. if (settings.CopyEquipment != null) CopyEquipment(original, clone.Value, settings.CopyEquipment.Value, settings.EquipmentWhitelist, settings.EquipmentBlacklist); // Copy storage on the mob itself as well. // This is needed for slime storage. if (settings.CopyInternalStorage) CopyStorage(original, clone.Value, settings.EquipmentWhitelist, settings.EquipmentBlacklist); // copy implants and their storage contents if (settings.CopyImplants) CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.EquipmentWhitelist, settings.EquipmentBlacklist); // Copy permanent status effects if (settings.CopyStatusEffects) CopyStatusEffects(original, clone.Value, settings.StatusEffectWhitelist, settings.StatusEffectBlacklist); var originalName = _nameMod.GetBaseName(original); // Set the clone's name. The raised events will also adjust their PDA and ID card names. _metaData.SetEntityName(clone.Value, originalName, raiseEvents: settings.RaiseEntityRenamedEvent); _metaData.SetEntityDescription(clone.Value, Description(original)); _identity.QueueIdentityUpdate(clone.Value); // We have to manually refresh the identity in case we did not raise events. _adminLogger.Add(LogType.Chat, LogImpact.Medium, $"The body of {original:player} was cloned as {clone.Value:player}"); return true; } public override void CloneComponents( EntityUid original, EntityUid clone, ProtoId settings) { if (!ProtoMan.Resolve(settings, out var proto)) return; CloneComponents(original, clone, proto); } public override void CloneComponents( EntityUid original, EntityUid clone, CloningSettingsPrototype settings) { var componentsToCopy = settings.Components; var componentsToEvent = settings.EventComponents; // don't make status effects permanent if (TryComp(original, out var statusComp)) { var statusComps = statusComp.ActiveEffects.Values.Select(s => s.RelevantComponent).Where(s => s != null).ToList(); componentsToCopy.ExceptWith(statusComps!); componentsToEvent.ExceptWith(statusComps!); } foreach (var componentName in componentsToCopy) { if (!Factory.TryGetRegistration(componentName, out var componentRegistration)) { Log.Error($"Tried to use invalid component registration for cloning: {componentName}"); continue; } // If the original does not have the component, then the clone shouldn't have it either. RemComp(clone, componentRegistration.Type); if (TryComp(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component? { CopyComp(original, clone, sourceComp); } } foreach (var componentName in componentsToEvent) { if (!Factory.TryGetRegistration(componentName, out var componentRegistration)) { Log.Error($"Tried to use invalid component registration for cloning: {componentName}"); continue; } // If the original does not have the component, then the clone shouldn't have it either. if (!HasComp(original, componentRegistration.Type)) RemComp(clone, componentRegistration.Type); } var cloningEv = new CloningEvent(settings, clone); RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied using CopyComp } public override void CopyEquipment( Entity original, Entity clone, SlotFlags slotFlags, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { if (!Resolve(original, ref original.Comp) || !Resolve(clone, ref clone.Comp)) return; var coords = Transform(clone).Coordinates; // Iterate over all inventory slots var slotEnumerator = _inventory.GetSlotEnumerator(original, slotFlags); while (slotEnumerator.NextItem(out var item, out var slot)) { var cloneItem = CopyItem(item, coords, whitelist, blacklist); if (cloneItem != null && !_inventory.TryEquip(clone, cloneItem.Value, slot.Name, silent: true, inventory: clone.Comp)) Del(cloneItem); // delete it again if the clone cannot equip it } } public override EntityUid? CopyItem( EntityUid original, EntityCoordinates coords, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { // we use a whitelist and blacklist to be sure to exclude any problematic entities if (!_whitelist.CheckBoth(original, blacklist, whitelist)) return null; var prototype = MetaData(original).EntityPrototype?.ID; if (prototype == null) return null; var spawned = SpawnAtPosition(prototype, coords); // copy over important component data var ev = new CloningItemEvent(spawned); RaiseLocalEvent(original, ref ev); // if the original has items inside its storage, copy those as well if (TryComp(original, out var originalStorage) && TryComp(spawned, out var spawnedStorage)) { // remove all items that spawned with the entity inside its storage // this ignores other containers, but this should be good enough for our purposes _container.CleanContainer(spawnedStorage.Container); // recursively replace them // surely no one will ever create two items that contain each other causing an infinite loop, right? foreach ((var itemUid, var itemLocation) in originalStorage.StoredItems) { var copy = CopyItem(itemUid, coords, whitelist, blacklist); if (copy != null) _storage.InsertAt((spawned, spawnedStorage), copy.Value, itemLocation, out _, playSound: false); } } return spawned; } public override void CopyStorage( Entity original, Entity target, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { if (!Resolve(original, ref original.Comp, false) || !Resolve(target, ref target.Comp, false)) return; var coords = Transform(target).Coordinates; // delete all items in the target storage _container.CleanContainer(target.Comp.Container); // recursively replace them foreach ((var itemUid, var itemLocation) in original.Comp.StoredItems) { var copy = CopyItem(itemUid, coords, whitelist, blacklist); if (copy != null) _storage.InsertAt(target, copy.Value, itemLocation, out _, playSound: false); } } public override void CopyImplants( Entity original, EntityUid target, bool copyStorage = false, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { if (!Resolve(original, ref original.Comp, false)) return; // they don't have any implants to copy! foreach (var originalImplant in original.Comp.ImplantContainer.ContainedEntities) { if (!HasComp(originalImplant)) continue; // not an implant (should only happen with admin shenanigans) var implantId = MetaData(originalImplant).EntityPrototype?.ID; if (implantId == null) continue; var targetImplant = _subdermalImplant.AddImplant(target, implantId); if (targetImplant == null) continue; // copy over important component data var ev = new CloningItemEvent(targetImplant.Value); RaiseLocalEvent(originalImplant, ref ev); if (copyStorage) CopyStorage(originalImplant, targetImplant.Value, whitelist, blacklist); // only needed for storage implants } } }