using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Robust.Shared.Containers { /// /// Holds data about a set of entity containers on this entity. /// [RegisterComponent] [ComponentReference(typeof(IContainerManager))] public class ContainerManagerComponent : Component, IContainerManager { [Dependency] private readonly IRobustSerializer _serializer = default!; [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; [ViewVariables] [DataField("containers")] private Dictionary _containers = new(); /// public sealed override string Name => "ContainerContainer"; /// public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER; /// public override void OnRemove() { base.OnRemove(); // IContianer.Shutdown modifies the _containers collection foreach (var container in _containers.Values.ToArray()) { container.Shutdown(); } _containers.Clear(); } /// public override void Initialize() { base.Initialize(); foreach (var container in _containers) { var baseContainer = (BaseContainer)container.Value; baseContainer.Manager = this; baseContainer.ID = container.Key; } } /// public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { if (!(curState is ContainerManagerComponentState cast)) return; // Delete now-gone containers. List? toDelete = null; foreach (var (id, container) in _containers) { if (!cast.ContainerSet.Any(data => data.Id == id)) { container.Shutdown(); toDelete ??= new List(); toDelete.Add(id); } } if (toDelete != null) { foreach (var dead in toDelete) { _containers.Remove(dead); } } // Add new containers and update existing contents. foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet) { if (!_containers.TryGetValue(id, out var container)) { container = ContainerFactory(containerType, id); _containers.Add(id, container); } // sync show flag container.ShowContents = showEnts; container.OccludesLight = occludesLight; // Remove gone entities. List? toRemove = null; foreach (var entity in container.ContainedEntities) { if (!entityUids.Contains(entity.Uid)) { toRemove ??= new List(); toRemove.Add(entity); } } if (toRemove != null) { foreach (var goner in toRemove) { container.Remove(goner); } } // Add new entities. foreach (var uid in entityUids) { var entity = Owner.EntityManager.GetEntity(uid); if (!container.ContainedEntities.Contains(entity)) container.Insert(entity); } } } private IContainer ContainerFactory(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 = this; return newContainer; } /// public override ComponentState GetComponentState(ICommonSession player) { // naive implementation that just sends the full state of the component List containerSet = new(); foreach (var container in _containers.Values) { var uidArr = new EntityUid[container.ContainedEntities.Count]; for (var index = 0; index < container.ContainedEntities.Count; index++) { var iEntity = container.ContainedEntities[index]; uidArr[index] = iEntity.Uid; } var sContainer = new ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr); containerSet.Add(sContainer); } return new ContainerManagerComponentState(containerSet); } /// public T MakeContainer(string id) where T : IContainer { return (T) MakeContainer(id, typeof(T)); } /// public IContainer GetContainer(string id) { return _containers[id]; } /// public bool HasContainer(string id) { return _containers.ContainsKey(id); } /// public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container) { var ret = _containers.TryGetValue(id, out var cont); container = cont!; return ret; } /// public bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container) { foreach (var contain in _containers.Values) { if (!contain.Deleted && contain.Contains(entity)) { container = contain; return true; } } container = default; return false; } /// public bool ContainsEntity(IEntity entity) { foreach (var container in _containers.Values) { if (!container.Deleted && container.Contains(entity)) return true; } return false; } /// public void ForceRemove(IEntity entity) { foreach (var container in _containers.Values) { if (container.Contains(entity)) container.ForceRemove(entity); } } /// public void InternalContainerShutdown(IContainer container) { _containers.Remove(container.ID); } /// public bool Remove(IEntity entity) { foreach (var containers in _containers.Values) { if (containers.Contains(entity)) return containers.Remove(entity); } return true; // If we don't contain the entity, it will always be removed } /// protected override void Shutdown() { base.Shutdown(); // On shutdown we won't get to process remove events in the containers so this has to be manually done. foreach (var container in _containers.Values) { foreach (var containerEntity in container.ContainedEntities) { Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(containerEntity)); } } } 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; Dirty(); return container; } public AllContainersEnumerable GetAllContainers() { return new(this); } [Serializable, NetSerializable] internal class ContainerManagerComponentState : ComponentState { public List ContainerSet; public ContainerManagerComponentState(List containers) : base(NetIDs.CONTAINER_MANAGER) { ContainerSet = 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; } } } [DataDefinition] private struct ContainerPrototypeData : IPopulateDefaultValues { [DataField("entities")] public List Entities; [DataField("type")] public string? Type; public ContainerPrototypeData(List entities, string type) { Entities = entities; Type = type; } public void PopulateDefaultValues() { Entities = new List(); } } public readonly struct AllContainersEnumerable : IEnumerable { private readonly ContainerManagerComponent _manager; public AllContainersEnumerable(ContainerManagerComponent manager) { _manager = manager; } public AllContainersEnumerator GetEnumerator() { return new(_manager); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public struct AllContainersEnumerator : IEnumerator { private Dictionary.ValueCollection.Enumerator _enumerator; public AllContainersEnumerator(ContainerManagerComponent manager) { _enumerator = manager._containers.Values.GetEnumerator(); Current = default; } public bool MoveNext() { while (_enumerator.MoveNext()) { if (!_enumerator.Current.Deleted) { Current = _enumerator.Current; return true; } } return false; } void IEnumerator.Reset() { ((IEnumerator) _enumerator).Reset(); } [AllowNull] public IContainer Current { get; private set; } object IEnumerator.Current => Current; public void Dispose() { } } } }