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() {