From ac24be2fb7c7abf7509df60a8d204957fc82ac07 Mon Sep 17 00:00:00 2001 From: poklj Date: Tue, 6 May 2025 14:22:32 -0300 Subject: [PATCH] Split out the CloneComponents into its own method (#37155) * Split out the CloneComponents into its own method * CR - Move some extra info in - add TryComp for status effects - Move some remcomps around - Make Special event raising components to handle special components that reference entities that have ownership * CR - Extra recommendation on the prototype thanks slarti * Solve the yaml linter problem * CR - Typos, grammar and some extra Status effect * cleanup --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Content.Server/Cloning/CloningSystem.cs | 89 ++++++++++++------- .../Cloning/CloningSettingsPrototype.cs | 17 +++- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 0a0c5d2eff..adf7acb7bd 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -7,7 +7,7 @@ using Content.Shared.Humanoid; 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; @@ -36,6 +36,7 @@ public sealed partial class CloningSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!; + [Dependency] private readonly NameModifierSystem _nameMod = default!; /// /// Spawns a clone of the given humanoid mob at the specified location or in nullspace. @@ -60,30 +61,7 @@ public sealed partial class CloningSystem : EntitySystem clone = coords == null ? Spawn(speciesPrototype.Prototype) : Spawn(speciesPrototype.Prototype, coords.Value); _humanoidSystem.CloneAppearance(original, clone.Value); - var componentsToCopy = settings.Components; - - // don't make status effects permanent - if (TryComp(original, out var statusComp)) - componentsToCopy.ExceptWith(statusComp.ActiveEffects.Values.Select(s => s.RelevantComponent).Where(s => s != null)!); - - foreach (var componentName in componentsToCopy) - { - if (!_componentFactory.TryGetRegistration(componentName, out var componentRegistration)) - { - Log.Error($"Tried to use invalid component registration for cloning: {componentName}"); - continue; - } - - 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); - } - } - - var cloningEv = new CloningEvent(settings, clone.Value); - RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied + CloneComponents(original, clone.Value, settings); // Add equipment first so that SetEntityName also renames the ID card. if (settings.CopyEquipment != null) @@ -98,21 +76,66 @@ public sealed partial class CloningSystem : EntitySystem if (settings.CopyImplants) CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.Whitelist, settings.Blacklist); - var originalName = Name(original); - if (TryComp(original, out var nameModComp)) // if the originals name was modified, use the unmodified name - originalName = nameModComp.BaseName; + var originalName = _nameMod.GetBaseName(original); - // 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(clone.Value); + // 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; } + /// + /// Copy components from one entity to another based on a CloningSettingsPrototype. + /// + /// The orignal Entity to clone components from. + /// The target Entity to clone components to. + /// The clone settings prototype containing the list of components to clone. + 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(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 (!_componentFactory.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 (EntityManager.TryGetComponent(original, componentRegistration.Type, out var sourceComp)) // Does the original have this component? + { + CopyComp(original, clone, sourceComp); + } + } + + foreach (var componentName in componentsToEvent) + { + if (!_componentFactory.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); + } + + var cloningEv = new CloningEvent(settings, clone); + RaiseLocalEvent(original, ref cloningEv); // used for datafields that cannot be directly copied using CopyComp + } + /// /// Copies the equipment the original has to the clone. /// This uses the original prototype of the items, so any changes to components that are done after spawning are lost! diff --git a/Content.Shared/Cloning/CloningSettingsPrototype.cs b/Content.Shared/Cloning/CloningSettingsPrototype.cs index b5cfc0500d..8cd22f1167 100644 --- a/Content.Shared/Cloning/CloningSettingsPrototype.cs +++ b/Content.Shared/Cloning/CloningSettingsPrototype.cs @@ -1,7 +1,9 @@ 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; @@ -62,11 +64,20 @@ public sealed partial class CloningSettingsPrototype : IPrototype, IInheritingPr /// TODO: Make this not a string https://github.com/space-wizards/RobustToolbox/issues/5709 /// - /// 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. /// [DataField] [AlwaysPushInheritance] public HashSet Components = new(); + + /// + /// 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. + /// + [DataField] + [AlwaysPushInheritance] + public HashSet EventComponents = new(); }