1
0

CloningSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using Content.Server.Humanoid;
  2. using Content.Shared.Administration.Logs;
  3. using Content.Shared.Cloning;
  4. using Content.Shared.Cloning.Events;
  5. using Content.Shared.Database;
  6. using Content.Shared.Humanoid;
  7. using Content.Shared.Inventory;
  8. using Content.Shared.Implants;
  9. using Content.Shared.Implants.Components;
  10. using Content.Shared.NameModifier.Components;
  11. using Content.Shared.StatusEffect;
  12. using Content.Shared.Stacks;
  13. using Content.Shared.Storage;
  14. using Content.Shared.Storage.EntitySystems;
  15. using Content.Shared.Whitelist;
  16. using Robust.Shared.Containers;
  17. using Robust.Shared.Map;
  18. using Robust.Shared.Prototypes;
  19. using System.Diagnostics.CodeAnalysis;
  20. using System.Linq;
  21. namespace Content.Server.Cloning;
  22. /// <summary>
  23. /// System responsible for making a copy of a humanoid's body.
  24. /// For the cloning machines themselves look at CloningPodSystem, CloningConsoleSystem and MedicalScannerSystem instead.
  25. /// </summary>
  26. public sealed class CloningSystem : EntitySystem
  27. {
  28. [Dependency] private readonly IComponentFactory _componentFactory = default!;
  29. [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
  30. [Dependency] private readonly InventorySystem _inventory = default!;
  31. [Dependency] private readonly MetaDataSystem _metaData = default!;
  32. [Dependency] private readonly IPrototypeManager _prototype = default!;
  33. [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
  34. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  35. [Dependency] private readonly SharedContainerSystem _container = default!;
  36. [Dependency] private readonly SharedStorageSystem _storage = default!;
  37. [Dependency] private readonly SharedStackSystem _stack = default!;
  38. [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
  39. /// <summary>
  40. /// Spawns a clone of the given humanoid mob at the specified location or in nullspace.
  41. /// </summary>
  42. public bool TryCloning(EntityUid original, MapCoordinates? coords, ProtoId<CloningSettingsPrototype> settingsId, [NotNullWhen(true)] out EntityUid? clone)
  43. {
  44. clone = null;
  45. if (!_prototype.TryIndex(settingsId, out var settings))
  46. return false; // invalid settings
  47. if (!TryComp<HumanoidAppearanceComponent>(original, out var humanoid))
  48. return false; // whatever body was to be cloned, was not a humanoid
  49. if (!_prototype.TryIndex(humanoid.Species, out var speciesPrototype))
  50. return false; // invalid species
  51. var attemptEv = new CloningAttemptEvent(settings);
  52. RaiseLocalEvent(original, ref attemptEv);
  53. if (attemptEv.Cancelled && !settings.ForceCloning)
  54. return false; // cannot clone, for example due to the unrevivable trait
  55. clone = coords == null ? Spawn(speciesPrototype.Prototype) : Spawn(speciesPrototype.Prototype, coords.Value);
  56. _humanoidSystem.CloneAppearance(original, clone.Value);
  57. var componentsToCopy = settings.Components;
  58. // don't make status effects permanent
  59. if (TryComp<StatusEffectsComponent>(original, out var statusComp))
  60. componentsToCopy.ExceptWith(statusComp.ActiveEffects.Values.Select(s => s.RelevantComponent).Where(s => s != null)!);
  61. foreach (var componentName in componentsToCopy)
  62. {
  63. if (!_componentFactory.TryGetRegistration(componentName, out var componentRegistration))
  64. {
  65. Log.Error($"Tried to use invalid component registration for cloning: {componentName}");
  66. continue;
  67. }
  68. if (EntityManager.TryGetComponent(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component?
  69. {
  70. if (HasComp(clone.Value, componentRegistration.Type)) // CopyComp cannot overwrite existing components
  71. RemComp(clone.Value, componentRegistration.Type);
  72. CopyComp(original, clone.Value, sourceComp);
  73. }
  74. }
  75. var cloningEv = new CloningEvent(settings, clone.Value);
  76. RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied
  77. // Add equipment first so that SetEntityName also renames the ID card.
  78. if (settings.CopyEquipment != null)
  79. CopyEquipment(original, clone.Value, settings.CopyEquipment.Value, settings.Whitelist, settings.Blacklist);
  80. // Copy storage on the mob itself as well.
  81. // This is needed for slime storage.
  82. if (settings.CopyInternalStorage)
  83. CopyStorage(original, clone.Value, settings.Whitelist, settings.Blacklist);
  84. // copy implants and their storage contents
  85. if (settings.CopyImplants)
  86. CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.Whitelist, settings.Blacklist);
  87. var originalName = Name(original);
  88. if (TryComp<NameModifierComponent>(original, out var nameModComp)) // if the originals name was modified, use the unmodified name
  89. originalName = nameModComp.BaseName;
  90. // This will properly set the BaseName and EntityName for the clone.
  91. // Adding the component first before renaming will make sure RefreshNameModifers is called.
  92. // Without this the name would get reverted to Urist.
  93. // If the clone has no name modifiers, NameModifierComponent will be removed again.
  94. EnsureComp<NameModifierComponent>(clone.Value);
  95. _metaData.SetEntityName(clone.Value, originalName);
  96. _adminLogger.Add(LogType.Chat, LogImpact.Medium, $"The body of {original:player} was cloned as {clone.Value:player}");
  97. return true;
  98. }
  99. /// <summary>
  100. /// Copies the equipment the original has to the clone.
  101. /// This uses the original prototype of the items, so any changes to components that are done after spawning are lost!
  102. /// </summary>
  103. public void CopyEquipment(Entity<InventoryComponent?> original, Entity<InventoryComponent?> clone, SlotFlags slotFlags, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
  104. {
  105. if (!Resolve(original, ref original.Comp) || !Resolve(clone, ref clone.Comp))
  106. return;
  107. var coords = Transform(clone).Coordinates;
  108. // Iterate over all inventory slots
  109. var slotEnumerator = _inventory.GetSlotEnumerator(original, slotFlags);
  110. while (slotEnumerator.NextItem(out var item, out var slot))
  111. {
  112. var cloneItem = CopyItem(item, coords, whitelist, blacklist);
  113. if (cloneItem != null && !_inventory.TryEquip(clone, cloneItem.Value, slot.Name, silent: true, inventory: clone.Comp))
  114. Del(cloneItem); // delete it again if the clone cannot equip it
  115. }
  116. }
  117. /// <summary>
  118. /// Copies an item and its storage recursively, placing all items at the same position in grid storage.
  119. /// This uses the original prototype of the items, so any changes to components that are done after spawning are lost!
  120. /// </summary>
  121. /// <remarks>
  122. /// This is not perfect and only considers item in storage containers.
  123. /// Some components have their own additional spawn logic on map init, so we cannot just copy all containers.
  124. /// </remarks>
  125. public EntityUid? CopyItem(EntityUid original, EntityCoordinates coords, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
  126. {
  127. // we use a whitelist and blacklist to be sure to exclude any problematic entities
  128. if (!_whitelist.CheckBoth(original, blacklist, whitelist))
  129. return null;
  130. var prototype = MetaData(original).EntityPrototype?.ID;
  131. if (prototype == null)
  132. return null;
  133. var spawned = EntityManager.SpawnAtPosition(prototype, coords);
  134. // if the original is a stack, adjust the count of the copy
  135. if (TryComp<StackComponent>(original, out var originalStack) && TryComp<StackComponent>(spawned, out var spawnedStack))
  136. _stack.SetCount(spawned, originalStack.Count, spawnedStack);
  137. // if the original has items inside its storage, copy those as well
  138. if (TryComp<StorageComponent>(original, out var originalStorage) && TryComp<StorageComponent>(spawned, out var spawnedStorage))
  139. {
  140. // remove all items that spawned with the entity inside its storage
  141. // this ignores other containers, but this should be good enough for our purposes
  142. _container.CleanContainer(spawnedStorage.Container);
  143. // recursively replace them
  144. // surely no one will ever create two items that contain each other causing an infinite loop, right?
  145. foreach ((var itemUid, var itemLocation) in originalStorage.StoredItems)
  146. {
  147. var copy = CopyItem(itemUid, coords, whitelist, blacklist);
  148. if (copy != null)
  149. _storage.InsertAt((spawned, spawnedStorage), copy.Value, itemLocation, out _, playSound: false);
  150. }
  151. }
  152. return spawned;
  153. }
  154. /// <summary>
  155. /// Copies an item's storage recursively to another storage.
  156. /// The storage grids should have the same shape or it will drop on the floor.
  157. /// Basically the same as CopyItem, but we don't copy the outermost container.
  158. /// </summary>
  159. public void CopyStorage(Entity<StorageComponent?> original, Entity<StorageComponent?> target, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
  160. {
  161. if (!Resolve(original, ref original.Comp, false) || !Resolve(target, ref target.Comp, false))
  162. return;
  163. var coords = Transform(target).Coordinates;
  164. // delete all items in the target storage
  165. _container.CleanContainer(target.Comp.Container);
  166. // recursively replace them
  167. foreach ((var itemUid, var itemLocation) in original.Comp.StoredItems)
  168. {
  169. var copy = CopyItem(itemUid, coords, whitelist, blacklist);
  170. if (copy != null)
  171. _storage.InsertAt(target, copy.Value, itemLocation, out _, playSound: false);
  172. }
  173. }
  174. /// <summary>
  175. /// Copies all implants from one mob to another.
  176. /// Might result in duplicates if the target already has them.
  177. /// Can copy the storage inside a storage implant according to a whitelist and blacklist.
  178. /// </summary>
  179. /// <param name="original">Entity to copy implants from.</param>
  180. /// <param name="target">Entity to copy implants to.</param>
  181. /// <param name="copyStorage">If true will copy storage of the implants (E.g storage implant)</param>
  182. /// <param name="whitelist">Whitelist for the storage copy (If copyStorage is true)</param>
  183. /// <param name="blacklist">Blacklist for the storage copy (If copyStorage is true)</param>
  184. public void CopyImplants(Entity<ImplantedComponent?> original, EntityUid target, bool copyStorage = false, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
  185. {
  186. if (!Resolve(original, ref original.Comp, false))
  187. return; // they don't have any implants to copy!
  188. foreach (var originalImplant in original.Comp.ImplantContainer.ContainedEntities)
  189. {
  190. if (!HasComp<SubdermalImplantComponent>(originalImplant))
  191. continue; // not an implant (should only happen with admin shenanigans)
  192. var implantId = MetaData(originalImplant).EntityPrototype?.ID;
  193. if (implantId == null)
  194. continue;
  195. var targetImplant = _subdermalImplant.AddImplant(target, implantId);
  196. if (copyStorage && targetImplant != null)
  197. CopyStorage(originalImplant, targetImplant.Value, whitelist, blacklist); // only needed for storage implants
  198. }
  199. }
  200. }