diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs
index ec99bd4da8a..a5789aa5e4e 100644
--- a/Robust.Shared/GameObjects/EntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/EntityManager.Components.cs
@@ -1075,6 +1075,58 @@ public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId,
return TryGetComponent(uid.Value, netId, out component, meta);
}
+ ///
+ public bool CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null)
+ {
+ if (!MetaQuery.Resolve(target, ref meta))
+ {
+ component = null;
+ return false;
+ }
+
+ return CopyComponentInternal(source, target, sourceComponent, out component, meta);
+ }
+
+ ///
+ public bool CopyComponent(EntityUid source, EntityUid target, T sourceComponent, [NotNullWhen(true)] out T? component, MetaDataComponent? meta = null) where T : IComponent
+ {
+ component = default;
+
+ if (!MetaQuery.Resolve(target, ref meta))
+ return false;
+
+ return CopyComponentInternal(source, target, sourceComponent, out component, meta);
+ }
+
+ ///
+ public bool CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
+ {
+ if (!MetaQuery.Resolve(target, ref meta))
+ return false;
+
+ var allSuccessful = true;
+
+ foreach (var comp in sourceComponents)
+ {
+ if (!CopyComponentInternal(source, target, comp, out _, meta))
+ allSuccessful = false;
+ }
+
+ return allSuccessful;
+ }
+
+ private bool CopyComponentInternal(EntityUid source, EntityUid target, T sourceComponent, [NotNullWhen(true)] out T? component, MetaDataComponent meta) where T : IComponent
+ {
+ var compReg = ComponentFactory.GetRegistration(sourceComponent.GetType());
+ component = (T)ComponentFactory.GetComponent(compReg);
+
+ _serManager.CopyTo(sourceComponent, ref component, notNullableOverride: true);
+ component.Owner = target;
+
+ AddComponentInternal(target, component, compReg, true, false, meta);
+ return true;
+ }
+
public EntityQuery GetEntityQuery() where TComp1 : IComponent
{
var comps = _entTraitArray[CompIdx.ArrayIndex()];
diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs
index 0075987d002..03b0f8cf362 100644
--- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs
+++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs
@@ -565,6 +565,31 @@ protected bool TryGetEntityData(NetEntity nuid, [NotNullWhen(true)] out EntityUi
#endregion
+ #region Component Copy
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected bool CopyComp(EntityUid source, EntityUid target, IComponent sourceComponent, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null)
+ {
+ return EntityManager.CopyComponent(source, target, sourceComponent, out component, meta);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected bool CopyComp(EntityUid source, EntityUid target, T sourceComponent, [NotNullWhen(true)] out T? component, MetaDataComponent? meta = null) where T : IComponent
+ {
+ return EntityManager.CopyComponent(source, target, sourceComponent, out component, meta);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected bool CopyComps(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
+ {
+ return EntityManager.CopyComponents(source, target, meta, sourceComponents);
+ }
+
+ #endregion
+
#region Component Has
///
diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs
index 11937cde0fc..1ce02226697 100644
--- a/Robust.Shared/GameObjects/IEntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs
@@ -358,6 +358,39 @@ public partial interface IEntityManager
/// If the component existed in the entity.
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);
+ ///
+ /// Copy a single component from source to target entity.
+ ///
+ /// The source entity to copy from.
+ /// The target entity to copy to.
+ /// The source component instance to copy.
+ /// The copied component if successful.
+ /// Optional metadata of the target entity.
+ /// Whether the component was successfully copied.
+ bool CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);
+
+ ///
+ /// Copy a single component from source to target entity.
+ ///
+ /// The type of component to copy.
+ /// The source entity to copy from.
+ /// The target entity to copy to.
+ /// The source component instance to copy.
+ /// The copied component if successful.
+ /// Optional metadata of the target entity.
+ /// Whether the component was successfully copied.
+ bool CopyComponent(EntityUid source, EntityUid target, T sourceComponent, [NotNullWhen(true)] out T? component, MetaDataComponent? meta = null) where T : IComponent;
+
+ ///
+ /// Copy multiple components from source to target entity using existing component instances.
+ ///
+ /// The source entity to copy from.
+ /// The target entity to copy to.
+ /// Optional metadata of the target entity.
+ /// Array of component instances to copy.
+ /// Whether all components were successfully copied.
+ bool CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents);
+
///
/// Returns a cached struct enumerator with the specified component.
///
diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityManagerCopyTests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityManagerCopyTests.cs
new file mode 100644
index 00000000000..6827403ee92
--- /dev/null
+++ b/Robust.UnitTesting/Shared/GameObjects/EntityManagerCopyTests.cs
@@ -0,0 +1,128 @@
+using System.Numerics;
+using NUnit.Framework;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.UnitTesting.Server;
+
+namespace Robust.UnitTesting.Shared.GameObjects;
+
+[TestFixture]
+public sealed partial class EntityManagerCopyTests
+{
+ [Test]
+ public void CopyComponentGeneric()
+ {
+ var instant = RobustServerSimulation.NewSimulation();
+ instant.RegisterComponents(fac =>
+ {
+ fac.RegisterClass();
+ });
+
+ var sim = instant.InitializeInstance();
+ var entManager = sim.Resolve();
+ var mapSystem = entManager.System();
+
+ mapSystem.CreateMap(out var mapId);
+
+ var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ var comp = entManager.AddComponent(original);
+
+ Assert.That(comp.Value, Is.EqualTo(false));
+ comp.Value = true;
+
+ var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ Assert.That(!entManager.HasComponent(target));
+
+ entManager.CopyComponent(original, target, comp, out var targetComp);
+
+ Assert.That(targetComp!.Owner == target);
+ Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
+ Assert.That(!ReferenceEquals(comp, targetComp));
+ }
+
+ [Test]
+ public void CopyComponentNonGeneric()
+ {
+ var instant = RobustServerSimulation.NewSimulation();
+ instant.RegisterComponents(fac =>
+ {
+ fac.RegisterClass();
+ });
+
+ var sim = instant.InitializeInstance();
+ var entManager = sim.Resolve();
+ var mapSystem = entManager.System();
+
+ mapSystem.CreateMap(out var mapId);
+
+ var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ var comp = entManager.AddComponent(original);
+
+ Assert.That(comp.Value, Is.EqualTo(false));
+ comp.Value = true;
+
+ var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ Assert.That(!entManager.HasComponent(target));
+
+ entManager.CopyComponent(original, target, (IComponent) comp, out var targetComp);
+
+ Assert.That(targetComp!.Owner == target);
+ Assert.That(((AComponent) targetComp).Value, Is.EqualTo(comp.Value));
+ Assert.That(!ReferenceEquals(comp, targetComp));
+ }
+
+ [Test]
+ public void CopyComponentMultiple()
+ {
+ var instant = RobustServerSimulation.NewSimulation();
+ instant.RegisterComponents(fac =>
+ {
+ fac.RegisterClass();
+ fac.RegisterClass();
+ });
+
+ var sim = instant.InitializeInstance();
+ var entManager = sim.Resolve();
+ var mapSystem = entManager.System();
+
+ mapSystem.CreateMap(out var mapId);
+
+ var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ var comp = entManager.AddComponent(original);
+ var comp2 = entManager.AddComponent(original);
+
+ Assert.That(comp.Value, Is.EqualTo(false));
+ comp.Value = true;
+
+ var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
+ Assert.That(!entManager.HasComponent(target));
+
+ entManager.CopyComponents(original, target, null, comp, comp2);
+ var targetComp = entManager.GetComponent(target);
+ var targetComp2 = entManager.GetComponent(target);
+
+ Assert.That(targetComp!.Owner == target);
+ Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
+
+ Assert.That(targetComp2!.Owner == target);
+ Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
+
+ Assert.That(!ReferenceEquals(comp, targetComp));
+ Assert.That(!ReferenceEquals(comp2, targetComp2));
+ }
+
+ [DataDefinition]
+ private sealed partial class AComponent : Component
+ {
+ [DataField]
+ public bool Value = false;
+ }
+
+ [DataDefinition]
+ private sealed partial class BComponent : Component
+ {
+ [DataField]
+ public bool Value = false;
+ }
+}
diff --git a/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs b/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs
index 28dc8c5c2f7..5601356abde 100644
--- a/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs
+++ b/Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs
@@ -1,12 +1,14 @@
+using System.Numerics;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
+using Robust.Shared.Serialization.Manager.Attributes;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.GameObjects
{
[TestFixture, Parallelizable]
- sealed class EntityManagerTests
+ sealed partial class EntityManagerTests
{
private static ISimulation SimulationFactory()
{