CatStation/Content.Shared/Cloning/SharedCloningSystem.cs
Pok 87d00d9d0e
Changeling no longer clones harmful status effects (#43778)
* Status-effect-cloning

* review

* Update tags.yml

* TraitStatusEffectDebuff

* different approach perhaps

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
2026-07-03 13:44:27 +00:00

173 lines
7 KiB
C#

using System.Diagnostics.CodeAnalysis;
using Content.Shared.Implants.Components;
using Content.Shared.Inventory;
using Content.Shared.StatusEffectNew.Components;
using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared.Cloning;
public abstract partial class SharedCloningSystem : EntitySystem
{
[Dependency] private StatusEffectNew.StatusEffectsSystem _statusEffects = default!; //TODO: This system has to support both the old and new status effect systems, until the old is able to be fully removed.
[Dependency] private EntityQuery<CloneableStatusEffectComponent> _cloneableEffectQuery = default!;
[Dependency] private EntityWhitelistSystem _whitelist = default!;
/// <summary>
/// Spawns a clone of the given humanoid mob at the specified location or in nullspace.
/// </summary>
public virtual bool TryCloning(
EntityUid original,
MapCoordinates? coords,
ProtoId<CloningSettingsPrototype> settingsId,
[NotNullWhen(true)] out EntityUid? clone)
{
clone = null;
return false;
}
/// <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 id containing the list of components to clone.</param>
public virtual void CloneComponents(
EntityUid original,
EntityUid clone,
ProtoId<CloningSettingsPrototype> settings)
{
}
/// <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 virtual void CloneComponents(
EntityUid original,
EntityUid clone,
CloningSettingsPrototype settings)
{
}
/// <summary>
/// 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!
/// </summary>
public virtual void CopyEquipment(
Entity<InventoryComponent?> original,
Entity<InventoryComponent?> clone,
SlotFlags slotFlags,
EntityWhitelist? whitelist = null,
EntityWhitelist? blacklist = null)
{
}
/// <summary>
/// Copies an item and its storage recursively, placing all items at the same position in grid storage.
/// This uses the original prototype of the items, so any changes to components that are done after spawning are lost!
/// </summary>
/// <remarks>
/// This is not perfect and only considers item in storage containers.
/// Some components have their own additional spawn logic on map init, so we cannot just copy all containers.
/// </remarks>
public virtual EntityUid? CopyItem(
EntityUid original,
EntityCoordinates coords,
EntityWhitelist? whitelist = null,
EntityWhitelist? blacklist = null)
{
return null;
}
/// <summary>
/// Copies an item's storage recursively to another storage.
/// The storage grids should have the same shape or it will drop on the floor.
/// Basically the same as CopyItem, but we don't copy the outermost container.
/// </summary>
public virtual void CopyStorage(
Entity<StorageComponent?> original,
Entity<StorageComponent?> target,
EntityWhitelist? whitelist = null,
EntityWhitelist? blacklist = null)
{
}
/// <summary>
/// Copies all implants from one mob to another.
/// Might result in duplicates if the target already has them.
/// Can copy the storage inside a storage implant according to a whitelist and blacklist.
/// </summary>
/// <param name="original">Entity to copy implants from.</param>
/// <param name="target">Entity to copy implants to.</param>
/// <param name="copyStorage">If true will copy storage of the implants (E.g storage implant)</param>
/// <param name="whitelist">Whitelist for the storage copy (If copyStorage is true)</param>
/// <param name="blacklist">Blacklist for the storage copy (If copyStorage is true)</param>
public virtual void CopyImplants(
Entity<ImplantedComponent?> original,
EntityUid target,
bool copyStorage = false,
EntityWhitelist? whitelist = null,
EntityWhitelist? blacklist = null)
{
}
/// <summary>
/// Scans all permanent status effects applied to the original entity and transfers them to the clone.
/// </summary>
/// <param name="original">The entity to take status effects from</param>
/// <param name="target">The entity to copy the status effects to.</param>
/// <param name="whitelist">The whitelist the status effects need to be in to get copied.</param>
/// <param name="blacklist">The blacklist the status effects cannot be in to get copied.</param>
/// <param name="removeMissing">Whether to remove clonable status effects from the target before applying new ones.</param>
public void CopyStatusEffects(
Entity<StatusEffectContainerComponent?> original,
Entity<StatusEffectContainerComponent?> target,
EntityWhitelist? whitelist = null,
EntityWhitelist? blacklist = null,
bool removeMissing = true)
{
if (removeMissing)
{
foreach (var effect in _statusEffects.EnumerateStatusEffects(target, _cloneableEffectQuery))
{
if (effect.Comp1.EndEffectTime is not null)
continue;
// Don't delete status effects that were never in the whitelist.
if (!_whitelist.CheckBoth(effect.Owner, blacklist, whitelist))
continue;
var effectProto = Prototype(effect);
if (effectProto is null)
continue;
// Remove all status effects we do not need anymore.
// We clone permanent ones, so we assume we replace them with a new set of permanent ones.
_statusEffects.TryRemoveStatusEffect(target, effectProto);
}
}
foreach (var effect in _statusEffects.EnumerateStatusEffects(original, _cloneableEffectQuery))
{
// We are not interested in temporary effects, only permanent ones.
if (effect.Comp1.EndEffectTime is not null)
continue;
if (!_whitelist.CheckBoth(effect.Owner, blacklist, whitelist))
continue;
var effectProto = Prototype(effect);
if (effectProto is null)
continue;
_statusEffects.TrySetStatusEffectDuration(target, effectProto);
}
}
}