diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs index 8986c2ba57..713a0f4d6d 100644 --- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Robust.Shared.Physics.Components; using static Robust.Shared.Containers.ContainerManagerComponent; namespace Robust.Client.GameObjects @@ -24,7 +25,7 @@ namespace Robust.Client.GameObjects private readonly HashSet _updateQueue = new(); - public readonly Dictionary ExpectedEntities = new(); + public readonly Dictionary ExpectedEntities = new(); public override void Initialize() { @@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects base.Shutdown(); } - protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing) + protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) { DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing)); } @@ -52,6 +53,9 @@ namespace Robust.Client.GameObjects if (!RemoveExpectedEntity(uid, out var container)) return; + if (container.Deleted) + return; + container.Insert(uid); } @@ -68,11 +72,8 @@ namespace Robust.Client.GameObjects var toDelete = new ValueList(); foreach (var (id, container) in component.Containers) { - if (cast.Containers.TryGetValue(id, out var stateContainer) - && stateContainer.GetType() == container.GetType()) - { + if (cast.Containers.ContainsKey(id)) continue; - } foreach (var entity in container.ContainedEntities.ToArray()) { @@ -97,26 +98,26 @@ namespace Robust.Client.GameObjects // Add new containers and update existing contents. - foreach (var (id, stateContainer) in cast.Containers) + foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.Containers.Values) { - DebugTools.AssertNotNull(stateContainer.ContainedEntities); if (!component.Containers.TryGetValue(id, out var container)) { - container = _dynFactory.CreateInstanceUnchecked(stateContainer.GetType(), inject: false); - container.Init(id, uid, component); + container = ContainerFactory(component, containerType, id); component.Containers.Add(id, container); } - DebugTools.Assert(container.ID == id); - container.ShowContents = stateContainer.ShowContents; - container.OccludesLight = stateContainer.OccludesLight; + // sync show flag + container.ShowContents = showEnts; + container.OccludesLight = occludesLight; // Remove gone entities. var toRemove = new ValueList(); foreach (var entity in container.ContainedEntities) { - if (!stateContainer.Contains(entity)) + if (!entityUids.Contains(entity)) + { toRemove.Add(entity); + } } foreach (var entity in toRemove) @@ -136,8 +137,10 @@ namespace Robust.Client.GameObjects var removedExpected = new ValueList(); foreach (var entityUid in container.ExpectedEntities) { - if (!stateContainer.Contains(entityUid)) + if (!entityUids.Contains(entityUid)) + { removedExpected.Add(entityUid); + } } foreach (var entityUid in removedExpected) @@ -146,7 +149,7 @@ namespace Robust.Client.GameObjects } // Add new entities. - foreach (var entity in stateContainer.ContainedEntities) + foreach (var entity in entityUids) { if (!EntityManager.TryGetComponent(entity, out MetaDataComponent? meta)) { @@ -205,10 +208,24 @@ namespace Robust.Client.GameObjects return; } + if (container.Deleted) + return; + container.Insert(message.Entity, EntityManager); } - public void AddExpectedEntity(EntityUid uid, BaseContainer container) + private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id) + { + var type = _serializer.FindSerializedType(typeof(IContainer), containerType); + if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found."); + + var newContainer = _dynFactory.CreateInstanceUnchecked(type); + newContainer.ID = id; + newContainer.Manager = component; + return newContainer; + } + + public void AddExpectedEntity(EntityUid uid, IContainer container) { DebugTools.Assert(!TryComp(uid, out MetaDataComponent? meta) || (meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached, @@ -230,7 +247,7 @@ namespace Robust.Client.GameObjects container.ExpectedEntities.Add(uid); } - public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container) + public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out IContainer? container) { if (!ExpectedEntities.Remove(uid, out container)) return false; diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 784589c762..0865dff9f9 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -1000,7 +1000,7 @@ namespace Robust.Client.GameStates // In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS. // In those situations, we need to add the entity back to the list of expected entities after detaching. - BaseContainer? container = null; + IContainer? container = null; if ((meta.Flags & MetaDataFlags.InContainer) != 0 && metas.TryGetComponent(xform.ParentUid, out var containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0 && @@ -1238,7 +1238,7 @@ namespace Robust.Client.GameStates var xform = _entities.GetComponent(uid); if (xform.ParentUid.IsValid()) { - BaseContainer? container = null; + IContainer? container = null; if ((meta.Flags & MetaDataFlags.InContainer) != 0 && _entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0) diff --git a/Robust.Server/Containers/ContainerSystem.cs b/Robust.Server/Containers/ContainerSystem.cs index d298fc2b48..1a632e2a35 100644 --- a/Robust.Server/Containers/ContainerSystem.cs +++ b/Robust.Server/Containers/ContainerSystem.cs @@ -6,7 +6,7 @@ namespace Robust.Server.Containers { public sealed class ContainerSystem : SharedContainerSystem { - protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing) + protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) { Log.Error($"Missing entity for container {ToPrettyString(uid)}. Missing uid: {missing}"); //cont.InternalRemove(ent); diff --git a/Robust.Shared/Containers/BaseContainer.cs b/Robust.Shared/Containers/BaseContainer.cs index 7aa13c0016..0881823041 100644 --- a/Robust.Shared/Containers/BaseContainer.cs +++ b/Robust.Shared/Containers/BaseContainer.cs @@ -12,81 +12,57 @@ using Robust.Shared.ViewVariables; using System; using System.Collections.Generic; using System.Numerics; -using Robust.Shared.Serialization; +using Robust.Shared.Map.Components; namespace Robust.Shared.Containers { /// /// Base container class that all container inherit from. /// - [ImplicitDataDefinitionForInheritors] - [Serializable, NetSerializable] - public abstract partial class BaseContainer + public abstract partial class BaseContainer : IContainer { - /// - /// Readonly collection of all the entities contained within this specific container - /// + /// [ViewVariables] public abstract IReadOnlyList ContainedEntities { get; } - [ViewVariables, NonSerialized] - public List ExpectedEntities = new(); + [ViewVariables] + public abstract List ExpectedEntities { get; } - /// - /// The ID of this container. - /// - [ViewVariables, NonSerialized, Access(typeof(SharedContainerSystem), typeof(ContainerManagerComponent))] - public string ID = default!; + /// + public abstract string ContainerType { get; } - [NonSerialized] - internal ContainerManagerComponent Manager = default!; + /// + [ViewVariables] + public bool Deleted { get; private set; } - /// - /// Prevents light from escaping the container, from ex. a flashlight. - /// + /// + [ViewVariables] + public string ID { get; internal set; } = default!; // Make sure you set me in init + + /// + public ContainerManagerComponent Manager { get; internal set; } = default!; // Make sure you set me in init + + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("occludes")] public bool OccludesLight { get; set; } = true; - /// - /// The entity that owns this container. - /// + /// [ViewVariables] public EntityUid Owner => Manager.Owner; - /// - /// Should the contents of this container be shown? False for closed containers like lockers, true for - /// things like glass display cases. - /// + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("showEnts")] public bool ShowContents { get; set; } - internal void Init(string id, EntityUid owner, ContainerManagerComponent component) - { - DebugTools.AssertNull(ID); - ID = id; - Manager = component; - - // TODO fix container init. - // Eventually, we want an owner field, but currently it needs to use component.Owner - // Owner = owner; - } - /// - /// Attempts to insert the entity into this container. + /// DO NOT CALL THIS METHOD DIRECTLY! + /// You want instead. /// - /// - /// If the insertion is successful, the inserted entity will end up parented to the - /// container entity, and the inserted entity's local position will be set to the zero vector. - /// - /// The entity to insert. - /// - /// False if the entity could not be inserted. - /// - /// Thrown if this container is a child of the entity, - /// which would cause infinite loops. - /// + protected BaseContainer() { } + + /// public bool Insert( EntityUid toinsert, IEntityManager? entMan = null, @@ -96,6 +72,7 @@ namespace Robust.Shared.Containers PhysicsComponent? physics = null, bool force = false) { + DebugTools.Assert(!Deleted); DebugTools.Assert(transform == null || transform.Owner == toinsert); DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner); DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner); @@ -103,6 +80,10 @@ namespace Robust.Shared.Containers DebugTools.Assert(!ExpectedEntities.Contains(toinsert)); IoCManager.Resolve(ref entMan); + //Verify we can insert into this container + if (!force && !CanInsert(toinsert, entMan)) + return false; + var physicsQuery = entMan.GetEntityQuery(); var transformQuery = entMan.GetEntityQuery(); var jointQuery = entMan.GetEntityQuery(); @@ -110,11 +91,6 @@ namespace Robust.Shared.Containers // ECS containers when var physicsSys = entMan.EntitySysManager.GetEntitySystem(); var jointSys = entMan.EntitySysManager.GetEntitySystem(); - var containerSys = entMan.EntitySysManager.GetEntitySystem(); - - //Verify we can insert into this container - if (!force && !containerSys.CanInsert(toinsert, this)) - return false; // Please somebody ecs containers var lookupSys = entMan.EntitySysManager.GetEntitySystem(); @@ -238,22 +214,45 @@ namespace Robust.Shared.Containers } } - /// - /// Whether the given entity can be inserted into this container. - /// - /// Whether to assume that the container is currently empty. - protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true; + /// + public virtual bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) + { + DebugTools.Assert(!Deleted); - /// - /// Attempts to remove the entity from this container. - /// - /// If false, this operation will not rigger a move or parent change event. Ignored if - /// destination is not null - /// If true, this will not perform can-remove checks. - /// Where to place the entity after removing. Avoids unnecessary broadphase updates. - /// If not specified, and reparent option is true, then the entity will either be inserted into a parent - /// container, the grid, or the map. - /// Optional final local rotation after removal. Avoids redundant move events. + // cannot insert into itself. + if (Owner == toinsert) + return false; + + IoCManager.Resolve(ref entMan); + + // no, you can't put maps or grids into containers + if (entMan.HasComponent(toinsert) || entMan.HasComponent(toinsert)) + return false; + + var xformSystem = entMan.EntitySysManager.GetEntitySystem(); + var xformQuery = entMan.GetEntityQuery(); + + // Crucial, prevent circular insertion. + if (xformSystem.ContainsEntity(xformQuery.GetComponent(toinsert), Owner, xformQuery)) + return false; + + //Improvement: Traverse the entire tree to make sure we are not creating a loop. + + //raise events + var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(this, toinsert); + entMan.EventBus.RaiseLocalEvent(Owner, insertAttemptEvent, true); + if (insertAttemptEvent.Cancelled) + return false; + + var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(this, toinsert); + entMan.EventBus.RaiseLocalEvent(toinsert, gettingInsertedAttemptEvent, true); + if (gettingInsertedAttemptEvent.Cancelled) + return false; + + return true; + } + + /// public bool Remove( EntityUid toRemove, IEntityManager? entMan = null, @@ -265,6 +264,7 @@ namespace Robust.Shared.Containers Angle? localRotation = null) { IoCManager.Resolve(ref entMan); + DebugTools.Assert(!Deleted); DebugTools.AssertNotNull(Manager); DebugTools.Assert(entMan.EntityExists(toRemove)); DebugTools.Assert(xform == null || xform.Owner == toRemove); @@ -273,8 +273,7 @@ namespace Robust.Shared.Containers xform ??= entMan.GetComponent(toRemove); meta ??= entMan.GetComponent(toRemove); - var sys = entMan.EntitySysManager.GetEntitySystem(); - if (!force && !sys.CanRemove(toRemove, this)) + if (!force && !CanRemove(toRemove, entMan)) return false; if (force && !Contains(toRemove)) @@ -306,7 +305,7 @@ namespace Robust.Shared.Containers else if (reparent) { // Container ECS when. - sys.AttachParentToContainerOrGrid(xform); + entMan.EntitySysManager.GetEntitySystem().AttachParentToContainerOrGrid(xform); if (localRotation != null) entMan.EntitySysManager.GetEntitySystem().SetLocalRotation(xform, localRotation.Value); } @@ -337,22 +336,40 @@ namespace Robust.Shared.Containers public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null) => Remove(toRemove, entMan, meta: meta, reparent: false, force: true); - /// - /// Checks if the entity is contained in this container. - /// This is not recursive, so containers of children are not checked. - /// - /// The entity to check. - /// True if the entity is immediately contained in this container, false otherwise. + /// + public virtual bool CanRemove(EntityUid toRemove, IEntityManager? entMan = null) + { + DebugTools.Assert(!Deleted); + + if (!Contains(toRemove)) + return false; + + IoCManager.Resolve(ref entMan); + + //raise events + var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(this, toRemove); + entMan.EventBus.RaiseLocalEvent(Owner, removeAttemptEvent, true); + if (removeAttemptEvent.Cancelled) + return false; + + var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(this, toRemove); + entMan.EventBus.RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true); + if (gettingRemovedAttemptEvent.Cancelled) + return false; + + return true; + } + + /// public abstract bool Contains(EntityUid contained); - /// - /// Clears the container and marks it as deleted. - /// + /// public void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null) { IoCManager.Resolve(ref entMan, ref netMan); InternalShutdown(entMan, netMan.IsClient); Manager.Containers.Remove(ID); + Deleted = true; } /// diff --git a/Robust.Shared/Containers/Container.cs b/Robust.Shared/Containers/Container.cs index 851d09ee77..1ed8d52624 100644 --- a/Robust.Shared/Containers/Container.cs +++ b/Robust.Shared/Containers/Container.cs @@ -1,11 +1,9 @@ -using System; using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Containers @@ -17,18 +15,27 @@ namespace Robust.Shared.Containers /// For example, inventory containers should be modified only through an inventory component. /// [UsedImplicitly] - [Serializable, NetSerializable] + [SerializedType(ClassName)] public sealed partial class Container : BaseContainer { + private const string ClassName = "Container"; + /// /// The generic container class uses a list of entities /// [DataField("ents")] private List _containerList = new(); + private readonly List _expectedEntities = new(); + /// public override IReadOnlyList ContainedEntities => _containerList; + public override List ExpectedEntities => _expectedEntities; + + /// + public override string ContainerType => ClassName; + /// protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { @@ -49,9 +56,6 @@ namespace Robust.Shared.Containers return false; #if DEBUG - if (IoCManager.Resolve().ApplyingState) - return true; - var entMan = IoCManager.Resolve(); var flags = entMan.GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}"); diff --git a/Robust.Shared/Containers/ContainerHelpers.cs b/Robust.Shared/Containers/ContainerHelpers.cs index b50c7ac9bf..0aea962b86 100644 --- a/Robust.Shared/Containers/ContainerHelpers.cs +++ b/Robust.Shared/Containers/ContainerHelpers.cs @@ -54,7 +54,7 @@ namespace Robust.Shared.Containers /// The container that this entity is inside of. /// If a container was found. [Obsolete("Use ContainerSystem.TryGetContainingContainer() instead")] - public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out BaseContainer? container, IEntityManager? entMan = null) + public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out IContainer? container, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); DebugTools.Assert(entMan.EntityExists(entity)); @@ -100,7 +100,7 @@ namespace Robust.Shared.Containers /// /// [Obsolete("Use SharedContainerSystem.EmptyContainer() instead")] - public static void EmptyContainer(this BaseContainer container, bool force = false, EntityCoordinates? moveTo = null, + public static void EmptyContainer(this IContainer container, bool force = false, EntityCoordinates? moveTo = null, bool attachToGridOrMap = false, IEntityManager? entMan = null) { IoCManager.Resolve().GetEntitySystem() @@ -112,7 +112,7 @@ namespace Robust.Shared.Containers /// /// [Obsolete("Use SharedContainerSystem.CleanContainer() instead")] - public static void CleanContainer(this BaseContainer container, IEntityManager? entMan = null) + public static void CleanContainer(this IContainer container, IEntityManager? entMan = null) { IoCManager.Resolve().GetEntitySystem() .CleanContainer(container); @@ -147,10 +147,10 @@ namespace Robust.Shared.Containers /// /// The new container. /// Thrown if there already is a container with the specified ID. - /// + /// [Obsolete("Use ContainerSystem.MakeContainer() instead")] public static T CreateContainer(this EntityUid entity, string containerId, IEntityManager? entMan = null) - where T : BaseContainer + where T : IContainer { IoCManager.Resolve(ref entMan); var containermanager = entMan.EnsureComponent(entity); @@ -159,7 +159,7 @@ namespace Robust.Shared.Containers [Obsolete("Use ContainerSystem.EnsureContainer() instead")] public static T EnsureContainer(this EntityUid entity, string containerId, IEntityManager? entMan = null) - where T : BaseContainer + where T : IContainer { IoCManager.Resolve(ref entMan); return EnsureContainer(entity, containerId, out _, entMan); @@ -167,7 +167,7 @@ namespace Robust.Shared.Containers [Obsolete("Use ContainerSystem.EnsureContainer() instead")] public static T EnsureContainer(this EntityUid entity, string containerId, out bool alreadyExisted, IEntityManager? entMan = null) - where T : BaseContainer + where T : IContainer { IoCManager.Resolve(ref entMan); var containerManager = entMan.EnsureComponent(entity); diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs index da485f09c6..3e0785bc23 100644 --- a/Robust.Shared/Containers/ContainerManagerComponent.cs +++ b/Robust.Shared/Containers/ContainerManagerComponent.cs @@ -25,16 +25,18 @@ namespace Robust.Shared.Containers [Dependency] private readonly INetManager _netMan = default!; [DataField("containers")] - public Dictionary Containers = new(); + public Dictionary Containers = new(); void ISerializationHooks.AfterDeserialization() { - // TODO custom type serializer - // TODO set owner uid on init. + // TODO remove ISerializationHooks I guess the IDs can be set by a custom serializer for the dictionary? But + // the component??? Maybe other systems need to stop assuming that containers have been initialized during + // their own init. foreach (var (id, container) in Containers) { - container.Manager = this; - container.ID = id; + var baseContainer = (BaseContainer) container; + baseContainer.Manager = this; + baseContainer.ID = id; } } @@ -53,20 +55,13 @@ namespace Robust.Shared.Containers /// public T MakeContainer(string id) - where T : BaseContainer + where T : IContainer { - if (HasContainer(id)) - throw new ArgumentException($"Container with specified ID already exists: '{id}'"); - - var container = _dynFactory.CreateInstanceUnchecked(typeof(T), inject: false); - container.Init(id, Owner, this); - Containers[id] = container; - _entMan.Dirty(this); - return container; + return (T) MakeContainer(id, typeof(T)); } /// - public BaseContainer GetContainer(string id) + public IContainer GetContainer(string id) { return Containers[id]; } @@ -78,7 +73,7 @@ namespace Robust.Shared.Containers } /// - public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container) + public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container) { var ret = Containers.TryGetValue(id, out var cont); container = cont!; @@ -86,11 +81,11 @@ namespace Robust.Shared.Containers } /// - public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container) + public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out IContainer? container) { foreach (var contain in Containers.Values) { - if (contain.Contains(entity)) + if (!contain.Deleted && contain.Contains(entity)) { container = contain; return true; @@ -106,7 +101,7 @@ namespace Robust.Shared.Containers { foreach (var container in Containers.Values) { - if (container.Contains(entity)) return true; + if (!container.Deleted && container.Contains(entity)) return true; } return false; @@ -130,6 +125,19 @@ namespace Robust.Shared.Containers return true; // If we don't contain the entity, it will always be removed } + private IContainer MakeContainer(string id, Type type) + { + if (HasContainer(id)) throw new ArgumentException($"Container with specified ID already exists: '{id}'"); + + var container = _dynFactory.CreateInstanceUnchecked(type); + container.ID = id; + container.Manager = this; + + Containers[id] = container; + _entMan.Dirty(this); + return container; + } + public AllContainersEnumerable GetAllContainers() { return new(this); @@ -138,15 +146,60 @@ namespace Robust.Shared.Containers [Serializable, NetSerializable] internal sealed class ContainerManagerComponentState : ComponentState { - public Dictionary Containers; + public Dictionary Containers; - public ContainerManagerComponentState(Dictionary containers) + public ContainerManagerComponentState(Dictionary containers) { Containers = containers; } + + [Serializable, NetSerializable] + public readonly struct ContainerData + { + public readonly string ContainerType; + public readonly string Id; + public readonly bool ShowContents; + public readonly bool OccludesLight; + public readonly EntityUid[] ContainedEntities; + + public ContainerData(string containerType, string id, bool showContents, bool occludesLight, EntityUid[] containedEntities) + { + ContainerType = containerType; + Id = id; + ShowContents = showContents; + OccludesLight = occludesLight; + ContainedEntities = containedEntities; + } + + public void Deconstruct(out string type, out string id, out bool showEnts, out bool occludesLight, out EntityUid[] ents) + { + type = ContainerType; + id = Id; + showEnts = ShowContents; + occludesLight = OccludesLight; + ents = ContainedEntities; + } + } } - public readonly struct AllContainersEnumerable : IEnumerable + [DataDefinition] + private partial struct ContainerPrototypeData + { + [DataField("entities")] public List Entities = new (); + + [DataField("type")] public string? Type = null; + + // explicit parameterless constructor is required. + public ContainerPrototypeData() { } + + public ContainerPrototypeData(List entities, string type) + { + Entities = entities; + Type = type; + } + } + + public readonly struct AllContainersEnumerable : IEnumerable { private readonly ContainerManagerComponent? _manager; @@ -160,7 +213,7 @@ namespace Robust.Shared.Containers return new(_manager); } - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -171,9 +224,9 @@ namespace Robust.Shared.Containers } } - public struct AllContainersEnumerator : IEnumerator + public struct AllContainersEnumerator : IEnumerator { - private Dictionary.ValueCollection.Enumerator _enumerator; + private Dictionary.ValueCollection.Enumerator _enumerator; public AllContainersEnumerator(ContainerManagerComponent? manager) { @@ -185,8 +238,11 @@ namespace Robust.Shared.Containers { while (_enumerator.MoveNext()) { - Current = _enumerator.Current; - return true; + if (!_enumerator.Current.Deleted) + { + Current = _enumerator.Current; + return true; + } } return false; @@ -194,11 +250,11 @@ namespace Robust.Shared.Containers void IEnumerator.Reset() { - ((IEnumerator) _enumerator).Reset(); + ((IEnumerator) _enumerator).Reset(); } [AllowNull] - public BaseContainer Current { get; private set; } + public IContainer Current { get; private set; } object IEnumerator.Current => Current; diff --git a/Robust.Shared/Containers/ContainerSlot.cs b/Robust.Shared/Containers/ContainerSlot.cs index 2ffbc95ecc..27785a2572 100644 --- a/Robust.Shared/Containers/ContainerSlot.cs +++ b/Robust.Shared/Containers/ContainerSlot.cs @@ -6,25 +6,24 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Containers { [UsedImplicitly] - [Serializable, NetSerializable] + [SerializedType(ClassName)] public sealed partial class ContainerSlot : BaseContainer { + private const string ClassName = "ContainerSlot"; + /// public override IReadOnlyList ContainedEntities { get { - if (_containedEntity == null) + if (ContainedEntity == null) return Array.Empty(); - _containedEntityArray ??= new[] { _containedEntity.Value }; - DebugTools.Assert(_containedEntityArray[0] == _containedEntity); return _containedEntityArray; } } @@ -37,18 +36,39 @@ namespace Robust.Shared.Containers { _containedEntity = value; if (value != null) - { - _containedEntityArray ??= new EntityUid[1]; - _containedEntityArray[0] = value.Value; - } + _containedEntityArray[0] = value!.Value; } } - private EntityUid? _containedEntity; + public override List ExpectedEntities => _expectedEntities; + private EntityUid? _containedEntity; + private readonly List _expectedEntities = new(); // Used by ContainedEntities to avoid allocating. - [NonSerialized] - private EntityUid[]? _containedEntityArray; + private readonly EntityUid[] _containedEntityArray = new EntityUid[1]; + + /// + public override string ContainerType => ClassName; + + /// + public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) + { + return (ContainedEntity == null) && CanInsertIfEmpty(toinsert, entMan); + } + + /// + /// Checks if the entity can be inserted into this container, assuming that the container slot is empty. + /// + /// + /// Useful if you need to know whether an item could be inserted into a slot, without having to actually eject + /// the currently contained entity first. + /// + /// The entity to attempt to insert. + /// True if the entity could be inserted into an empty slot, false otherwise. + public bool CanInsertIfEmpty(EntityUid toinsert, IEntityManager? entMan = null) + { + return base.CanInsert(toinsert, entMan); + } /// public override bool Contains(EntityUid contained) @@ -57,9 +77,6 @@ namespace Robust.Shared.Containers return false; #if DEBUG - if (IoCManager.Resolve().ApplyingState) - return true; - var entMan = IoCManager.Resolve(); var flags = entMan.GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}"); diff --git a/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs b/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs index 15b63d7801..aa61077027 100644 --- a/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs +++ b/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs @@ -6,10 +6,10 @@ namespace Robust.Shared.Containers; public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs { - public readonly BaseContainer Container; + public readonly IContainer Container; public readonly EntityUid EntityUid; - public ContainerAttemptEventBase(BaseContainer container, EntityUid entityUid) + public ContainerAttemptEventBase(IContainer container, EntityUid entityUid) { Container = container; EntityUid = entityUid; @@ -18,44 +18,28 @@ public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase { - /// - /// If true, this check should assume that the container is currently empty. - /// I.e., could the entity be inserted if the container doesn't contain anything else? - /// - public bool AssumeEmpty { get; set; } - - public ContainerIsInsertingAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty) - : base(container, entityUid) + public ContainerIsInsertingAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) { - AssumeEmpty = assumeEmpty; } } public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEventBase { - /// - /// If true, this check should assume that the container is currently empty. - /// I.e., could the entity be inserted if the container doesn't contain anything else? - /// - public bool AssumeEmpty { get; set; } - - public ContainerGettingInsertedAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty) - : base(container, entityUid) + public ContainerGettingInsertedAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) { - AssumeEmpty = assumeEmpty; } } public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase { - public ContainerIsRemovingAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid) + public ContainerIsRemovingAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) { } } public sealed class ContainerGettingRemovedAttemptEvent : ContainerAttemptEventBase { - public ContainerGettingRemovedAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid) + public ContainerGettingRemovedAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) { } } diff --git a/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs b/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs index 570e8dcbb8..06e869d035 100644 --- a/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs +++ b/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs @@ -12,14 +12,14 @@ namespace Robust.Shared.Containers /// /// The container being acted upon. /// - public BaseContainer Container { get; } + public IContainer Container { get; } /// /// The entity that was removed or inserted from/into the container. /// public EntityUid Entity { get; } - protected ContainerModifiedMessage(EntityUid entity, BaseContainer container) + protected ContainerModifiedMessage(EntityUid entity, IContainer container) { Entity = entity; Container = container; diff --git a/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs b/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs index cd044765ca..78ba4dbadd 100644 --- a/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs @@ -9,5 +9,5 @@ namespace Robust.Shared.Containers; [PublicAPI] public sealed class EntGotInsertedIntoContainerMessage : ContainerModifiedMessage { - public EntGotInsertedIntoContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } + public EntGotInsertedIntoContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } } diff --git a/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs b/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs index dfcae817c5..38622c21e4 100644 --- a/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs @@ -11,7 +11,7 @@ namespace Robust.Shared.Containers { public readonly EntityUid OldParent; - public EntInsertedIntoContainerMessage(EntityUid entity, EntityUid oldParent, BaseContainer container) : base(entity, container) + public EntInsertedIntoContainerMessage(EntityUid entity, EntityUid oldParent, IContainer container) : base(entity, container) { OldParent = oldParent; } diff --git a/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs b/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs index 2f75510da9..82b3bd5e3b 100644 --- a/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs @@ -9,7 +9,7 @@ namespace Robust.Shared.Containers [PublicAPI] public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage { - public EntRemovedFromContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } + public EntRemovedFromContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } } /// @@ -18,6 +18,6 @@ namespace Robust.Shared.Containers [PublicAPI] public sealed class EntGotRemovedFromContainerMessage : ContainerModifiedMessage { - public EntGotRemovedFromContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } + public EntGotRemovedFromContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } } } diff --git a/Robust.Shared/Containers/IContainer.cs b/Robust.Shared/Containers/IContainer.cs new file mode 100644 index 0000000000..134d412132 --- /dev/null +++ b/Robust.Shared/Containers/IContainer.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Containers +{ + /// + /// A container is a way to "contain" entities inside other entities, in a logical way. + /// This is alike BYOND's contents system, except more advanced. + /// + /// + ///

+ /// Containers are logical separations of entities contained inside another entity. + /// for example, a crate with two separated compartments would have two separate containers. + /// If an entity inside compartment A drops something, + /// the dropped entity would be placed in compartment A too, + /// and compartment B would be completely untouched. + ///

+ ///

+ /// Containers are managed by an entity's , + /// and have an ID to be referenced by. + ///

+ ///
+ /// + [PublicAPI] + [ImplicitDataDefinitionForInheritors] + public partial interface IContainer + { + /// + /// Readonly collection of all the entities contained within this specific container + /// + IReadOnlyList ContainedEntities { get; } + + List ExpectedEntities { get; } + + /// + /// The type of this container. + /// + string ContainerType { get; } + + /// + /// True if the container has been shut down via + /// + bool Deleted { get; } + + /// + /// The ID of this container. + /// + string ID { get; } + + /// + /// Prevents light from escaping the container, from ex. a flashlight. + /// + bool OccludesLight { get; set; } + + /// + /// The entity owning this container. + /// + EntityUid Owner { get; } + + /// + /// Should the contents of this container be shown? False for closed containers like lockers, true for + /// things like glass display cases. + /// + bool ShowContents { get; set; } + + /// + /// Checks if the entity can be inserted into this container. + /// + /// The entity to attempt to insert. + /// + /// True if the entity can be inserted, false otherwise. + bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null); + + /// + /// Attempts to insert the entity into this container. + /// + /// + /// If the insertion is successful, the inserted entity will end up parented to the + /// container entity, and the inserted entity's local position will be set to the zero vector. + /// + /// The entity to insert. + /// + /// False if the entity could not be inserted. + /// + /// Thrown if this container is a child of the entity, + /// which would cause infinite loops. + /// + bool Insert(EntityUid toinsert, + IEntityManager? entMan = null, + TransformComponent? transform = null, + TransformComponent? ownerTransform = null, + MetaDataComponent? meta = null, + PhysicsComponent? physics = null, + bool force = false); + + /// + /// Checks if the entity can be removed from this container. + /// + /// The entity to check. + /// + /// True if the entity can be removed, false otherwise. + bool CanRemove(EntityUid toremove, IEntityManager? entMan = null); + + /// + /// Attempts to remove the entity from this container. + /// + /// If false, this operation will not rigger a move or parent change event. Ignored if + /// destination is not null + /// If true, this will not perform can-remove checks. + /// Where to place the entity after removing. Avoids unnecessary broadphase updates. + /// If not specified, and reparent option is true, then the entity will either be inserted into a parent + /// container, the grid, or the map. + /// Optional final local rotation after removal. Avoids redundant move events. + bool Remove( + EntityUid toremove, + IEntityManager? entMan = null, + TransformComponent? xform = null, + MetaDataComponent? meta = null, + bool reparent = true, + bool force = false, + EntityCoordinates? destination = null, + Angle? localRotation = null); + + [Obsolete("use force option in Remove()")] + void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null); + + /// + /// Checks if the entity is contained in this container. + /// This is not recursive, so containers of children are not checked. + /// + /// The entity to check. + /// True if the entity is immediately contained in this container, false otherwise. + bool Contains(EntityUid contained); + + /// + /// Clears the container and marks it as deleted. + /// + void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null); + } +} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs deleted file mode 100644 index 246d55f327..0000000000 --- a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Robust.Shared.GameObjects; -using Robust.Shared.Utility; - -namespace Robust.Shared.Containers; - -public abstract partial class SharedContainerSystem -{ - /// - /// Checks if the entity can be inserted into the given container. - /// - /// If true, this will check whether the entity could be inserted if the container were - /// empty. - public bool CanInsert( - EntityUid toInsert, - BaseContainer container, - TransformComponent? toInsertXform = null, - bool assumeEmpty = false) - { - if (container.Owner == toInsert) - return false; - - if (!assumeEmpty && container.Contains(toInsert)) - return false; - - if (!container.CanInsert(toInsert, assumeEmpty, EntityManager)) - return false; - - if (!_xforms.Resolve(toInsert, ref toInsertXform)) - return false; - - // no, you can't put maps or grids into containers - if (_mapQuery.HasComponent(toInsert) || _gridQuery.HasComponent(toInsert)) - return false; - - // Prevent circular insertion. - if (_transform.ContainsEntity(toInsertXform, container.Owner)) - return false; - - var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(container, toInsert, assumeEmpty); - RaiseLocalEvent(container.Owner, insertAttemptEvent, true); - if (insertAttemptEvent.Cancelled) - return false; - - var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(container, toInsert, assumeEmpty); - RaiseLocalEvent(toInsert, gettingInsertedAttemptEvent, true); - - return !gettingInsertedAttemptEvent.Cancelled; - } -} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Remove.cs b/Robust.Shared/Containers/SharedContainerSystem.Remove.cs deleted file mode 100644 index 7662ae7b50..0000000000 --- a/Robust.Shared/Containers/SharedContainerSystem.Remove.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Robust.Shared.Containers; - -public abstract partial class SharedContainerSystem -{ - /// - /// Checks if the entity can be removed from this container. - /// - /// True if the entity can be removed, false otherwise. - public bool CanRemove(EntityUid toRemove, BaseContainer container) - { - if (!container.Contains(toRemove)) - return false; - - //raise events - var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(container, toRemove); - RaiseLocalEvent(container.Owner, removeAttemptEvent, true); - if (removeAttemptEvent.Cancelled) - return false; - - var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(container, toRemove); - RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true); - return !gettingRemovedAttemptEvent.Cancelled; - } -} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Validation.cs b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs index 253347b49b..3ea0a6f239 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.Validation.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs @@ -54,7 +54,7 @@ public abstract partial class SharedContainerSystem : EntitySystem } } - protected abstract void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing); + protected abstract void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing); private void ValidateChildren(TransformComponent xform, EntityQuery xformQuery, EntityQuery physicsQuery) { diff --git a/Robust.Shared/Containers/SharedContainerSystem.cs b/Robust.Shared/Containers/SharedContainerSystem.cs index 47f016ff20..f9bf030961 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.cs @@ -6,7 +6,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; @@ -17,10 +16,6 @@ namespace Robust.Shared.Containers { [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - private EntityQuery _gridQuery; - private EntityQuery _mapQuery; private EntityQuery _metas; private EntityQuery _xforms; @@ -34,15 +29,29 @@ namespace Robust.Shared.Containers SubscribeLocalEvent(OnStartupValidation); SubscribeLocalEvent(OnContainerGetState); - _gridQuery = GetEntityQuery(); - _mapQuery = GetEntityQuery(); _metas = EntityManager.GetEntityQuery(); _xforms = EntityManager.GetEntityQuery(); } private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args) { - args.State = new ContainerManagerComponent.ContainerManagerComponentState(component.Containers); + // naive implementation that just sends the full state of the component + Dictionary containerSet = new(component.Containers.Count); + + foreach (var container in component.Containers.Values) + { + var uidArr = new EntityUid[container.ContainedEntities.Count]; + + for (var index = 0; index < container.ContainedEntities.Count; index++) + { + uidArr[index] = container.ContainedEntities[index]; + } + + var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr); + containerSet.Add(container.ID, sContainer); + } + + args.State = new ContainerManagerComponent.ContainerManagerComponentState(containerSet); } // TODO: Make ContainerManagerComponent ECS and make these proxy methods the real deal. @@ -50,7 +59,7 @@ namespace Robust.Shared.Containers #region Proxy Methods public T MakeContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) - where T : BaseContainer + where T : IContainer { if (!Resolve(uid, ref containerManager, false)) containerManager = EntityManager.AddComponent(uid); // Happy Vera. @@ -59,7 +68,7 @@ namespace Robust.Shared.Containers } public T EnsureContainer(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null) - where T : BaseContainer + where T : IContainer { if (!Resolve(uid, ref containerManager, false)) containerManager = EntityManager.AddComponent(uid); @@ -79,12 +88,12 @@ namespace Robust.Shared.Containers } public T EnsureContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) - where T : BaseContainer + where T : IContainer { return EnsureContainer(uid, id, out _, containerManager); } - public BaseContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) + public IContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) { if (!Resolve(uid, ref containerManager)) throw new ArgumentException("Entity does not have a ContainerManagerComponent!", nameof(uid)); @@ -100,7 +109,7 @@ namespace Robust.Shared.Containers return containerManager.HasContainer(id); } - public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null) + public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null) { if (Resolve(uid, ref containerManager, false)) return containerManager.TryGetContainer(id, out container); @@ -109,7 +118,7 @@ namespace Robust.Shared.Containers return false; } - public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false) + public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false) { if (Resolve(uid, ref containerManager, false) && (skipExistCheck || EntityManager.EntityExists(containedUid))) return containerManager.TryGetContainer(containedUid, out container); @@ -155,7 +164,7 @@ namespace Robust.Shared.Containers #region Container Helpers - public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null) + public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out IContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null) { container = null; @@ -218,7 +227,7 @@ namespace Robust.Shared.Containers } /// - /// Finds the first instance of a component on the recursive parented containers that hold an entity + /// Finds the first instance of a component on the recursive parented containers that hold an entity /// public bool TryFindComponentOnEntityContainerOrParent( EntityUid uid, @@ -246,7 +255,7 @@ namespace Robust.Shared.Containers } /// - /// Finds all instances of a component on the recursive parented containers that hold an entity + /// Finds all instances of a component on the recursive parented containers that hold an entity /// public bool TryFindComponentsOnEntityContainerOrParent( EntityUid uid, @@ -326,8 +335,8 @@ namespace Robust.Shared.Containers public bool IsInSameOrTransparentContainer( EntityUid user, EntityUid other, - BaseContainer? userContainer = null, - BaseContainer? otherContainer = null, + IContainer? userContainer = null, + IContainer? otherContainer = null, bool userSeeInsideSelf = false) { if (userContainer == null) @@ -362,14 +371,14 @@ namespace Robust.Shared.Containers /// /// Gets the top-most container in the hierarchy for this entity, if it exists. /// - public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out BaseContainer? container) + public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container) { var xformQuery = EntityManager.GetEntityQuery(); return TryGetOuterContainer(uid, xform, out container, xformQuery); } public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, - [NotNullWhen(true)] out BaseContainer? container, EntityQuery xformQuery) + [NotNullWhen(true)] out IContainer? container, EntityQuery xformQuery) { container = null; @@ -439,7 +448,7 @@ namespace Robust.Shared.Containers /// Attempts to remove all entities in a container. Returns removed entities. ///
public List EmptyContainer( - BaseContainer container, + IContainer container, bool force = false, EntityCoordinates? destination = null, bool reparent = true) @@ -461,7 +470,7 @@ namespace Robust.Shared.Containers /// /// Attempts to remove and delete all entities in a container. /// - public void CleanContainer(BaseContainer container) + public void CleanContainer(IContainer container) { foreach (var ent in container.ContainedEntities.ToArray()) { @@ -482,7 +491,7 @@ namespace Robust.Shared.Containers transform.AttachToGridOrMap(); } - private bool TryInsertIntoContainer(TransformComponent transform, BaseContainer container) + private bool TryInsertIntoContainer(TransformComponent transform, IContainer container) { if (container.Insert(transform.Owner)) return true; diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs index 4525465f31..e365936441 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -9,8 +8,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Robust.Shared.Timing; using Robust.Shared.Utility; // ReSharper disable AccessToStaticMemberViaDerivedType @@ -67,6 +64,10 @@ namespace Robust.UnitTesting.Server.GameObjects.Components Assert.That(() => manager.GetContainer("dummy3"), Throws.TypeOf()); entManager.DeleteEntity(entity); + + Assert.That(manager.Deleted, Is.True); + Assert.That(container.Deleted, Is.True); + Assert.That(container2.Deleted, Is.True); } [Test] @@ -171,7 +172,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(entity), Is.False); - Assert.That(containerSys.CanInsert(entity, container), Is.False); + Assert.That(container.CanInsert(entity), Is.False); } [Test] @@ -185,7 +186,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(mapEnt), Is.False); - Assert.That(containerSys.CanInsert(mapEnt, container), Is.False); + Assert.That(container.CanInsert(mapEnt), Is.False); } [Test] @@ -199,7 +200,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(grid), Is.False); - Assert.That(containerSys.CanInsert(grid, container), Is.False); + Assert.That(container.CanInsert(grid), Is.False); } [Test] @@ -283,14 +284,13 @@ namespace Robust.UnitTesting.Server.GameObjects.Components Assert.That(state.Containers, Has.Count.EqualTo(1)); var cont = state.Containers.Values.First(); - Assert.That(cont.ID, Is.EqualTo("dummy")); + Assert.That(cont.Id, Is.EqualTo("dummy")); Assert.That(cont.OccludesLight, Is.True); Assert.That(cont.ShowContents, Is.True); - Assert.That(cont.ContainedEntities.Count, Is.EqualTo(1)); + Assert.That(cont.ContainedEntities.Length, Is.EqualTo(1)); Assert.That(cont.ContainedEntities[0], Is.EqualTo(childEnt)); } - [Serializable, NetSerializable] private sealed partial class ContainerOnlyContainer : BaseContainer { /// @@ -299,9 +299,13 @@ namespace Robust.UnitTesting.Server.GameObjects.Components private readonly List _containerList = new(); private readonly List _expectedEntities = new(); + public override string ContainerType => nameof(ContainerOnlyContainer); + /// public override IReadOnlyList ContainedEntities => _containerList; + public override List ExpectedEntities => _expectedEntities; + /// protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { @@ -320,9 +324,6 @@ namespace Robust.UnitTesting.Server.GameObjects.Components if (!_containerList.Contains(contained)) return false; - if (IoCManager.Resolve().ApplyingState) - return true; - var flags = IoCManager.Resolve().GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0); return true; @@ -340,9 +341,10 @@ namespace Robust.UnitTesting.Server.GameObjects.Components } } - protected internal override bool CanInsert(EntityUid toinsert, bool assumeEmpty, IEntityManager entMan) + public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) { - return entMan.HasComponent(toinsert); + IoCManager.Resolve(ref entMan); + return entMan.TryGetComponent(toinsert, out ContainerManagerComponent? _); } } } diff --git a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs index c177f28a6a..1b73fe5b7e 100644 --- a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs @@ -346,10 +346,10 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(containerComp.Containers.ContainsKey("testContainer")); - var BaseContainer = containerComp.GetContainer("testContainer"); - Assert.That(BaseContainer.ContainedEntities.Count, Is.EqualTo(1)); + var iContainer = containerComp.GetContainer("testContainer"); + Assert.That(iContainer.ContainedEntities.Count, Is.EqualTo(1)); - var containeeEnt = BaseContainer.ContainedEntities[0]; + var containeeEnt = iContainer.ContainedEntities[0]; Assert.That(entMan.GetComponent(containeeEnt).EntityName, Is.EqualTo("ContaineeEnt")); }); } diff --git a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs index 4a99b23a5d..e853dcf56b 100644 --- a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs +++ b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; @@ -6,7 +5,6 @@ using NUnit.Framework; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Serialization; namespace Robust.UnitTesting.Shared.Spawning; @@ -102,16 +100,18 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest /// /// Simple container that can store up to 2 entities. /// - [Serializable, NetSerializable] private sealed partial class TestContainer : BaseContainer { private readonly List _ents = new(); + private readonly List _expected = new(); + public override string ContainerType => nameof(TestContainer); public override IReadOnlyList ContainedEntities => _ents; + public override List ExpectedEntities => _expected; protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) => _ents.Add(toInsert); protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan) => _ents.Remove(toRemove); public override bool Contains(EntityUid contained) => _ents.Contains(contained); protected override void InternalShutdown(IEntityManager entMan, bool isClient) { } - protected internal override bool CanInsert(EntityUid toinsert, bool assumeEmpty, IEntityManager entMan) + public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) => _ents.Count < 2 && !_ents.Contains(toinsert); } }