diff --git a/Content.Client/Paper/UI/PaperVisualizerSystem.cs b/Content.Client/Paper/UI/PaperVisualizerSystem.cs index a0d05736ad..3fd717f896 100644 --- a/Content.Client/Paper/UI/PaperVisualizerSystem.cs +++ b/Content.Client/Paper/UI/PaperVisualizerSystem.cs @@ -11,13 +11,21 @@ public sealed class PaperVisualizerSystem : VisualizerSystem(uid, PaperVisuals.Status , out var writingStatus, args.Component)) + if (AppearanceSystem.TryGetData(uid, PaperVisuals.Status, out var writingStatus, args.Component)) args.Sprite.LayerSetVisible(PaperVisualLayers.Writing, writingStatus == PaperStatus.Written); if (AppearanceSystem.TryGetData(uid, PaperVisuals.Stamp, out var stampState, args.Component)) { - args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState); - args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true); + if (stampState != string.Empty) + { + args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState); + args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true); + } + else + { + args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, false); + } + } } } diff --git a/Content.Server/Cloning/CloningSystem.Subscriptions.cs b/Content.Server/Cloning/CloningSystem.Subscriptions.cs new file mode 100644 index 0000000000..659d9a1ea1 --- /dev/null +++ b/Content.Server/Cloning/CloningSystem.Subscriptions.cs @@ -0,0 +1,83 @@ +using Content.Server.Forensics; +using Content.Shared.Cloning.Events; +using Content.Shared.Clothing.Components; +using Content.Shared.FixedPoint; +using Content.Shared.Labels.Components; +using Content.Shared.Labels.EntitySystems; +using Content.Shared.Paper; +using Content.Shared.Stacks; +using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.Cloning; + +/// +/// The part of item cloning responsible for copying over important components. +/// This is used for . +/// Anything not copied over here gets reverted to the values the item had in its prototype. +/// +/// +/// This method of copying items is of course not perfect as we cannot clone every single component, which would be pretty much impossible with our ECS. +/// We only consider the most important components so the paradox clone gets similar equipment. +/// This method of using subscriptions was chosen to make it easy for forks to add their own custom components that need to be copied. +/// +public sealed partial class CloningSystem : EntitySystem +{ + [Dependency] private readonly SharedStackSystem _stack = default!; + [Dependency] private readonly SharedLabelSystem _label = default!; + [Dependency] private readonly ForensicsSystem _forensics = default!; + [Dependency] private readonly PaperSystem _paper = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCloneStack); + SubscribeLocalEvent(OnCloneLabel); + SubscribeLocalEvent(OnClonePaper); + SubscribeLocalEvent(OnCloneForensics); + SubscribeLocalEvent(OnCloneStore); + } + + private void OnCloneStack(Entity ent, ref CloningItemEvent args) + { + // if the clone is a stack as well, adjust the count of the copy + if (TryComp(args.CloneUid, out var cloneStackComp)) + _stack.SetCount(args.CloneUid, ent.Comp.Count, cloneStackComp); + } + + private void OnCloneLabel(Entity ent, ref CloningItemEvent args) + { + // copy the label + _label.Label(args.CloneUid, ent.Comp.CurrentLabel); + } + + private void OnClonePaper(Entity ent, ref CloningItemEvent args) + { + // copy the text and any stamps + if (TryComp(args.CloneUid, out var clonePaperComp)) + { + _paper.SetContent((args.CloneUid, clonePaperComp), ent.Comp.Content); + _paper.CopyStamps(ent.AsNullable(), (args.CloneUid, clonePaperComp)); + } + } + + private void OnCloneForensics(Entity ent, ref CloningItemEvent args) + { + // copy any forensics to the cloned item + _forensics.CopyForensicsFrom(ent.Comp, args.CloneUid); + } + + private void OnCloneStore(Entity ent, ref CloningItemEvent args) + { + // copy the current amount of currency in the store + // at the moment this takes care of uplink implants and the portable nukie uplinks + // turning a copied pda into an uplink will need some refactoring first + if (TryComp(args.CloneUid, out var cloneStoreComp)) + { + cloneStoreComp.Balance = new Dictionary, FixedPoint2>(ent.Comp.Balance); + } + } + +} diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index b8aff813d2..0a0c5d2eff 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -9,7 +9,6 @@ using Content.Shared.Implants; using Content.Shared.Implants.Components; using Content.Shared.NameModifier.Components; using Content.Shared.StatusEffect; -using Content.Shared.Stacks; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Content.Shared.Whitelist; @@ -25,7 +24,7 @@ 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 class CloningSystem : EntitySystem +public sealed partial class CloningSystem : EntitySystem { [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; @@ -36,7 +35,6 @@ public sealed class CloningSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; - [Dependency] private readonly SharedStackSystem _stack = default!; [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!; /// @@ -157,9 +155,9 @@ public sealed class CloningSystem : EntitySystem var spawned = EntityManager.SpawnAtPosition(prototype, coords); - // if the original is a stack, adjust the count of the copy - if (TryComp(original, out var originalStack) && TryComp(spawned, out var spawnedStack)) - _stack.SetCount(spawned, originalStack.Count, spawnedStack); + // 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)) @@ -232,7 +230,14 @@ public sealed class CloningSystem : EntitySystem var targetImplant = _subdermalImplant.AddImplant(target, implantId); - if (copyStorage && targetImplant != null) + 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 } diff --git a/Content.Server/Forensics/Systems/ForensicsSystem.cs b/Content.Server/Forensics/Systems/ForensicsSystem.cs index 27df086ae1..66818855e5 100644 --- a/Content.Server/Forensics/Systems/ForensicsSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicsSystem.cs @@ -138,6 +138,11 @@ namespace Content.Server.Forensics { dest.Fingerprints.Add(print); } + + foreach (var residue in src.Residues) + { + dest.Residues.Add(residue); + } } public List GetSolutionsDNA(EntityUid uid) diff --git a/Content.Shared/Cloning/CloningEvents.cs b/Content.Shared/Cloning/CloningEvents.cs index bd6645404c..7005ff2966 100644 --- a/Content.Shared/Cloning/CloningEvents.cs +++ b/Content.Shared/Cloning/CloningEvents.cs @@ -2,12 +2,21 @@ namespace Content.Shared.Cloning.Events; /// /// Raised before a mob is cloned. Cancel to prevent cloning. +/// This is raised on the original mob. /// [ByRefEvent] public record struct CloningAttemptEvent(CloningSettingsPrototype Settings, bool Cancelled = false); /// -/// Raised after a new mob got spawned when cloning a humanoid. +/// Raised after a new mob was spawned when cloning a humanoid. +/// This is raised on the original mob. /// [ByRefEvent] public record struct CloningEvent(CloningSettingsPrototype Settings, EntityUid CloneUid); + +/// +/// Raised after a new item was spawned when cloning an item. +/// This is raised on the original item. +/// +[ByRefEvent] +public record struct CloningItemEvent(EntityUid CloneUid); diff --git a/Content.Shared/Paper/PaperSystem.cs b/Content.Shared/Paper/PaperSystem.cs index bbff5ec39b..712133c0e6 100644 --- a/Content.Shared/Paper/PaperSystem.cs +++ b/Content.Shared/Paper/PaperSystem.cs @@ -224,6 +224,26 @@ public sealed class PaperSystem : EntitySystem return true; } + /// + /// Copy any stamp information from one piece of paper to another. + /// + public void CopyStamps(Entity source, Entity target) + { + if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp)) + return; + + target.Comp.StampedBy = new List(source.Comp.StampedBy); + target.Comp.StampState = source.Comp.StampState; + Dirty(target); + + if (TryComp(target, out var appearance)) + { + // delete any stamps if the stamp state is null + _appearance.SetData(target, PaperVisuals.Stamp, target.Comp.StampState ?? "", appearance); + } + } + + public void SetContent(Entity entity, string content) { entity.Comp.Content = content; diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index fa634f4774..5a913674e5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml @@ -61,6 +61,7 @@ components: - AttachedClothing # helmets, which are part of the suit - HumanoidAppearance # will cause problems for downstream felinids getting cloned as Urists + - Implanter # they will spawn full again, but you already get the implant. And we can't do item slot copying yet - VirtualItem # all antagonist roles