using Content.Shared.Inventory;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
-using Content.Shared.NameModifier.Components;
+using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.StatusEffect;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
+ [Dependency] private readonly NameModifierSystem _nameMod = default!;
/// <summary>
/// Spawns a clone of the given humanoid mob at the specified location or in nullspace.
clone = coords == null ? Spawn(speciesPrototype.Prototype) : Spawn(speciesPrototype.Prototype, coords.Value);
_humanoidSystem.CloneAppearance(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.Whitelist, settings.Blacklist);
+
+ // Copy storage on the mob itself as well.
+ // This is needed for slime storage.
+ if (settings.CopyInternalStorage)
+ CopyStorage(original, clone.Value, settings.Whitelist, settings.Blacklist);
+
+ // copy implants and their storage contents
+ if (settings.CopyImplants)
+ CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.Whitelist, settings.Blacklist);
+
+ 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);
+
+ _adminLogger.Add(LogType.Chat, LogImpact.Medium, $"The body of {original:player} was cloned as {clone.Value:player}");
+ return true;
+ }
+
+ /// <summary>
+ /// Copy components from one entity to another based on a CloningSettingsPrototype.
+ /// </summary>
+ /// <param name="original">The orignal Entity to clone components from.</param>
+ /// <param name="clone">The target Entity to clone components to.</param>
+ /// <param name="settings">The clone settings prototype containing the list of components to clone.</param>
+ public void CloneComponents(EntityUid original, EntityUid clone, CloningSettingsPrototype settings)
+ {
var componentsToCopy = settings.Components;
+ var componentsToEvent = settings.EventComponents;
// don't make status effects permanent
if (TryComp<StatusEffectsComponent>(original, out var statusComp))
- componentsToCopy.ExceptWith(statusComp.ActiveEffects.Values.Select(s => s.RelevantComponent).Where(s => s != null)!);
+ {
+ 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)
{
continue;
}
+ // If the original does not have the component, then the clone shouldn't have it either.
+ RemComp(clone, componentRegistration.Type);
if (EntityManager.TryGetComponent(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component?
{
- if (HasComp(clone.Value, componentRegistration.Type)) // CopyComp cannot overwrite existing components
- RemComp(clone.Value, componentRegistration.Type);
- CopyComp(original, clone.Value, sourceComp);
+ CopyComp(original, clone, sourceComp);
}
}
- var cloningEv = new CloningEvent(settings, clone.Value);
- RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied
-
- // Add equipment first so that SetEntityName also renames the ID card.
- if (settings.CopyEquipment != null)
- CopyEquipment(original, clone.Value, settings.CopyEquipment.Value, settings.Whitelist, settings.Blacklist);
-
- // Copy storage on the mob itself as well.
- // This is needed for slime storage.
- if (settings.CopyInternalStorage)
- CopyStorage(original, clone.Value, settings.Whitelist, settings.Blacklist);
-
- // copy implants and their storage contents
- if (settings.CopyImplants)
- CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.Whitelist, settings.Blacklist);
-
- var originalName = Name(original);
- if (TryComp<NameModifierComponent>(original, out var nameModComp)) // if the originals name was modified, use the unmodified name
- originalName = nameModComp.BaseName;
+ foreach (var componentName in componentsToEvent)
+ {
+ if (!_componentFactory.TryGetRegistration(componentName, out var componentRegistration))
+ {
+ Log.Error($"Tried to use invalid component registration for cloning: {componentName}");
+ continue;
+ }
- // This will properly set the BaseName and EntityName for the clone.
- // Adding the component first before renaming will make sure RefreshNameModifers is called.
- // Without this the name would get reverted to Urist.
- // If the clone has no name modifiers, NameModifierComponent will be removed again.
- EnsureComp<NameModifierComponent>(clone.Value);
- _metaData.SetEntityName(clone.Value, originalName);
+ // If the original does not have the component, then the clone shouldn't have it either.
+ RemComp(clone, componentRegistration.Type);
+ }
- _adminLogger.Add(LogType.Chat, LogImpact.Medium, $"The body of {original:player} was cloned as {clone.Value:player}");
- return true;
+ var cloningEv = new CloningEvent(settings, clone);
+ RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied using CopyComp
}
/// <summary>
using Content.Shared.Inventory;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Content.Shared.Cloning;
/// TODO: Make this not a string https://github.com/space-wizards/RobustToolbox/issues/5709
/// <summary>
- /// Components to copy from the original to the clone.
- /// This only makes a shallow copy of datafields!
- /// If you need a deep copy or additional component initialization, then subscribe to CloningEvent instead!
+ /// Components to copy from the original to the clone using CopyComp.
+ /// This makes a deepcopy of all datafields, including information the clone might not own!
+ /// If you need to exclude data or do additional component initialization, then subscribe to CloningEvent instead!
+ /// Components in this list that the orginal does not have will be removed from the clone.
/// </summary>
[DataField]
[AlwaysPushInheritance]
public HashSet<string> Components = new();
+
+ /// <summary>
+ /// Components to remove from the clone and copy over manually using a CloneEvent raised on the original.
+ /// Use this when the component cannot be copied using CopyComp, for example when having an Uid as a datafield.
+ ///</summary>
+ [DataField]
+ [AlwaysPushInheritance]
+ public HashSet<string> EventComponents = new();
}