Shared Containers (#1579)

* Added a basic server simulation framework for help with tests.

* Moved as much as possible to Robust.Shared/Containers.
Moved ContainerSlot from content to engine.

* Moved ClientContainer to shared.

* Merged client/server ContainerManagerComponents into a single shared version.

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

* More tests coverage.
Fixed bug with transferring items between containers.

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

* Attempted to fix dictionary serialization.
Logs warning when trying to check if an unknown GridId is paused.

* Remove OldCode.
This commit is contained in:
Acruid
2021-03-01 15:19:59 -08:00
committed by GitHub
parent ab95f39f9f
commit 24707b7385
28 changed files with 1314 additions and 996 deletions

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
@@ -49,9 +50,6 @@ namespace Robust.Client.GameObjects
Register<AnimationPlayerComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<TimerComponent>();
#if DEBUG

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent
{
[DebuggerDisplay("ClientContainer {Owner.Uid}/{ID}")]
private sealed class ClientContainer : IContainer
{
public List<IEntity> Entities { get; } = new List<IEntity>();
public ClientContainer(string id, ContainerManagerComponent manager)
{
ID = id;
Manager = manager;
}
[ViewVariables] public IContainerManager Manager { get; }
[ViewVariables] public string ID { get; }
[ViewVariables] public IEntity Owner => Manager.Owner;
[ViewVariables] public bool Deleted { get; private set; }
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => Entities;
[ViewVariables]
public bool ShowContents { get; set; }
[ViewVariables]
public bool OccludesLight { get; set; }
public bool CanInsert(IEntity toinsert)
{
return false;
}
public bool Insert(IEntity toinsert)
{
return false;
}
public bool CanRemove(IEntity toremove)
{
return false;
}
public bool Remove(IEntity toremove)
{
return false;
}
public void ForceRemove(IEntity toRemove)
{
throw new NotSupportedException("Cannot directly modify containers on the client");
}
public bool Contains(IEntity contained)
{
return Entities.Contains(contained);
}
public void DoInsert(IEntity entity)
{
Entities.Add(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void DoRemove(IEntity entity)
{
Entities.Remove(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void Shutdown()
{
Deleted = true;
}
}
public override void InternalContainerShutdown(IContainer container)
{
}
}
}

View File

@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent : SharedContainerManagerComponent
{
[ViewVariables]
private readonly Dictionary<string, ClientContainer> _containers = new();
public override T MakeContainer<T>(string id)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override bool Remove(IEntity entity)
{
// TODO: This will probably need relaxing if we want to predict things like inventories.
throw new NotSupportedException("Cannot modify containers on the client.");
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return _containers.Values.Where(c => !c.Deleted);
}
public override IContainer GetContainer(string id)
{
return _containers[id];
}
public override bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
public override 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 override bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if(!(curState is ContainerManagerComponentState cast))
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in _containers)
{
if (!cast.Containers.ContainsKey(id))
{
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
}
}
if (toDelete != null)
{
foreach (var dead in toDelete)
{
_containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (id, data) in cast.Containers)
{
if (!_containers.TryGetValue(id, out var container))
{
container = new ClientContainer(id, this);
_containers.Add(id, container);
}
// sync show flag
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.Entities)
{
if (!data.ContainedEntities.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
{
container.DoRemove(goner);
}
}
// Add new entities.
foreach (var uid in data.ContainedEntities)
{
var entity = Owner.EntityManager.GetEntity(uid);
if (!container.Entities.Contains(entity))
{
container.DoInsert(entity);
}
}
}
}
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.Entities)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new UpdateContainerOcclusionMessage(containerEntity));
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : EntitySystem
public class ClientContainerSystem : ContainerSystem
{
private readonly HashSet<IEntity> _updateQueue = new();
@@ -91,14 +91,4 @@ namespace Robust.Client.GameObjects
}
}
}
internal readonly struct UpdateContainerOcclusionMessage
{
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -1,359 +0,0 @@
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.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public sealed class ContainerManagerComponent : SharedContainerManagerComponent
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private readonly Dictionary<string, IContainer> EntityContainers = new();
private Dictionary<string, List<EntityUid>>? _entitiesWaitingResolve;
[ViewVariables] private IEnumerable<IContainer> _allContainers => EntityContainers.Values;
/// <summary>
/// Shortcut method to make creation of containers easier.
/// Creates a new container on the entity and gives it back to you.
/// </summary>
/// <param name="id">The ID of the new container.</param>
/// <param name="entity">The entity to create the container for.</param>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
/// <seealso cref="IContainerManager.MakeContainer{T}(string)" />
public static T Create<T>(string id, IEntity entity) where T : IContainer
{
if (!entity.TryGetComponent<IContainerManager>(out var containermanager))
{
containermanager = entity.AddComponent<ContainerManagerComponent>();
}
return containermanager.MakeContainer<T>(id);
}
public static T Ensure<T>(string id, IEntity entity) where T : IContainer
{
return Ensure<T>(id, entity, out _);
}
public static T Ensure<T>(string id, IEntity entity, out bool alreadyExisted) where T : IContainer
{
var containerManager = entity.EnsureComponent<ContainerManagerComponent>();
if (!containerManager.TryGetContainer(id, out var existing))
{
alreadyExisted = false;
return containerManager.MakeContainer<T>(id);
}
if (!(existing is T container))
{
throw new InvalidOperationException(
$"The container exists but is of a different type: {existing.GetType()}");
}
alreadyExisted = true;
return container;
}
public override T MakeContainer<T>(string id)
{
return (T) MakeContainer(id, typeof(T));
}
private IContainer MakeContainer(string id, Type type)
{
if (HasContainer(id))
{
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
}
var container = (IContainer) Activator.CreateInstance(type, id, this)!;
EntityContainers[id] = container;
Dirty();
return container;
}
public new AllContainersEnumerable GetAllContainers()
{
return new(this);
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return GetAllContainers();
}
/// <inheritdoc />
public override IContainer GetContainer(string id)
{
return EntityContainers[id];
}
/// <inheritdoc />
public override bool HasContainer(string id)
{
return EntityContainers.ContainsKey(id);
}
/// <inheritdoc />
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
if (!HasContainer(id))
{
container = null;
return false;
}
container = GetContainer(id);
return true;
}
public override bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
{
foreach (var contain in EntityContainers.Values)
{
if (!contain.Deleted && contain.Contains(entity))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public override bool ContainsEntity(IEntity entity)
{
foreach (var container in EntityContainers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
foreach (var container in EntityContainers.Values)
{
if (container.Contains(entity))
{
container.ForceRemove(entity);
}
}
}
public override void InternalContainerShutdown(IContainer container)
{
EntityContainers.Remove(container.ID);
}
/// <inheritdoc />
public override bool Remove(IEntity entity)
{
foreach (var containers in EntityContainers.Values)
{
if (containers.Contains(entity))
{
return containers.Remove(entity);
}
}
return true; // If we don't contain the entity, it will always be removed
}
public override void OnRemove()
{
base.OnRemove();
// IContianer.Shutdown modifies the EntityContainers collection
foreach (var container in EntityContainers.Values.ToArray())
{
container.Shutdown();
}
EntityContainers.Clear();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
if (serializer.Reading)
{
if (serializer.TryReadDataField<Dictionary<string, ContainerPrototypeData>>("containers", out var data))
{
_entitiesWaitingResolve = new Dictionary<string, List<EntityUid>>();
foreach (var (key, datum) in data)
{
if (datum.Type == null)
{
throw new InvalidOperationException("Container does not have type set.");
}
var type = _reflectionManager.LooseGetType(datum.Type);
MakeContainer(key, type);
if (datum.Entities.Count == 0)
{
continue;
}
var list = new List<EntityUid>(datum.Entities.Where(u => u.IsValid()));
_entitiesWaitingResolve.Add(key, list);
}
}
}
else
{
var dict = new Dictionary<string, ContainerPrototypeData>();
foreach (var (key, container) in EntityContainers)
{
var list = new List<EntityUid>(container.ContainedEntities.Select(e => e.Uid));
var data = new ContainerPrototypeData(list, container.GetType().FullName!);
dict.Add(key, data);
}
// ReSharper disable once RedundantTypeArgumentsOfMethod
serializer.DataWriteFunction<Dictionary<string, ContainerPrototypeData>?>("containers", null,
() => dict);
}
}
public override void Initialize()
{
base.Initialize();
if (_entitiesWaitingResolve == null)
{
return;
}
foreach (var (key, entities) in _entitiesWaitingResolve)
{
var container = GetContainer(key);
foreach (var uid in entities)
{
container.Insert(Owner.EntityManager.GetEntity(uid));
}
}
_entitiesWaitingResolve = null;
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new ContainerManagerComponentState(
_allContainers.ToDictionary(
c => c.ID,
DataFor));
}
private static ContainerManagerComponentState.ContainerData DataFor(IContainer container)
{
return new()
{
ContainedEntities = container.ContainedEntities.Select(e => e.Uid).ToArray(),
ShowContents = container.ShowContents,
OccludesLight = container.OccludesLight
};
}
private struct ContainerPrototypeData : IExposeData
{
public List<EntityUid> Entities;
public string? Type;
public ContainerPrototypeData(List<EntityUid> entities, string type)
{
Entities = entities;
Type = type;
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Entities, "entities", new List<EntityUid>());
serializer.DataField(ref Type, "type", null);
}
}
public struct AllContainersEnumerable : IEnumerable<IContainer>
{
private readonly ContainerManagerComponent _manager;
public AllContainersEnumerable(ContainerManagerComponent manager)
{
_manager = manager;
}
public AllContainersEnumerator GetEnumerator()
{
return new(_manager);
}
IEnumerator<IContainer> IEnumerable<IContainer>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public struct AllContainersEnumerator : IEnumerator<IContainer>
{
private Dictionary<string, IContainer>.ValueCollection.Enumerator _enumerator;
public AllContainersEnumerator(ContainerManagerComponent manager)
{
_enumerator = manager.EntityContainers.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<IContainer>) _enumerator).Reset();
}
[AllowNull] public IContainer Current { get; private set; }
object? IEnumerator.Current => Current;
public void Dispose()
{
}
}
}
}

View File

@@ -1,47 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
{
protected ContainerModifiedMessage(IEntity entity, IContainer container)
{
Entity = entity;
Container = container;
}
/// <summary>
/// The entity that was removed or inserted from/into the container.
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// The container being acted upon.
/// </summary>
public IContainer Container { get; }
}
/// <summary>
/// Raised when an entity is removed from a container.
/// </summary>
[PublicAPI]
public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage
{
public EntRemovedFromContainerMessage(IEntity entity, IContainer container) : base(entity, container)
{
}
}
/// <summary>
/// Raised when an entity is inserted into a container.
/// </summary>
[PublicAPI]
public sealed class EntInsertedIntoContainerMessage : ContainerModifiedMessage
{
public EntInsertedIntoContainerMessage(IEntity entity, IContainer container) : base(entity, container)
{
}
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
@@ -35,9 +36,6 @@ namespace Robust.Server.GameObjects
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteRenderableComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();

View File

@@ -6,6 +6,7 @@ using Prometheus;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;

View File

@@ -1,80 +1,23 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// Default implementation for containers,
/// cannot be inherited. If additional logic is needed,
/// this logic should go on the systems that are holding this container.
/// For example, inventory containers should be modified only through an inventory component.
/// </summary>
[UsedImplicitly]
public sealed class Container : BaseContainer
{
/// <summary>
/// The generic container class uses a list of entities
/// </summary>
private readonly List<IEntity> _containerList = new();
/// <inheritdoc />
public Container(string id, IContainerManager manager) : base(id, manager) { }
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
_containerList.Add(toinsert);
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
_containerList.Remove(toremove);
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
return _containerList.Contains(contained);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
foreach (var entity in _containerList)
{
entity.Delete();
}
}
}
/// <summary>
/// Base container class that all container inherit from.
/// </summary>
public abstract class BaseContainer : IContainer
{
/// <inheritdoc />
public IContainerManager Manager { get; private set; }
[ViewVariables]
public abstract IReadOnlyList<IEntity> ContainedEntities { get; }
/// <inheritdoc />
[ViewVariables]
public string ID { get; }
/// <inheritdoc />
[ViewVariables]
public IEntity Owner => Manager.Owner;
public abstract string ContainerType { get; }
/// <inheritdoc />
[ViewVariables]
@@ -82,47 +25,44 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
[ViewVariables]
public abstract IReadOnlyList<IEntity> ContainedEntities { get; }
public string ID { get; internal set; } = default!; // Make sure you set me in init
/// <inheritdoc />
public IContainerManager Manager { get; internal set; } = default!; // Make sure you set me in init
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool OccludesLight { get; set; } = true;
/// <inheritdoc />
[ViewVariables]
public IEntity Owner => Manager.Owner;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool ShowContents { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool OccludesLight { get; set; }
/// <summary>
/// DO NOT CALL THIS METHOD DIRECTLY!
/// You want <see cref="IContainerManager.MakeContainer{T}(string)" /> instead.
/// </summary>
protected BaseContainer(string id, IContainerManager manager)
{
DebugTools.Assert(!string.IsNullOrEmpty(id));
DebugTools.AssertNotNull(manager);
ID = id;
Manager = manager;
}
protected BaseContainer() { }
/// <inheritdoc />
public bool Insert(IEntity toinsert)
{
DebugTools.Assert(!Deleted);
//Verify we can insert and that the object got properly removed from its current location
//Verify we can insert into this container
if (!CanInsert(toinsert))
return false;
var transform = toinsert.Transform;
if (transform.Parent == null) // Only true if Parent is the map entity
return false;
// CanInsert already checks nullability of Parent (or container forgot to call base that does)
if (toinsert.TryGetContainerMan(out var containerManager) && !containerManager.Remove(toinsert))
return false; // Can't remove from existing container, can't insert.
if(transform.Parent.Owner.TryGetContainerMan(out var containerManager) && !containerManager.Remove(toinsert))
{
// Can't remove from existing container, can't insert.
return false;
}
InternalInsert(toinsert);
transform.AttachParent(Owner.Transform);
@@ -133,19 +73,6 @@ namespace Robust.Server.GameObjects
return true;
}
/// <summary>
/// Implement to store the reference in whatever form you want
/// </summary>
/// <param name="toinsert"></param>
protected virtual void InternalInsert(IEntity toinsert)
{
DebugTools.Assert(!Deleted);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(toinsert, this));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false));
Manager.Dirty();
}
/// <inheritdoc />
public virtual bool CanInsert(IEntity toinsert)
{
@@ -155,6 +82,10 @@ namespace Robust.Server.GameObjects
if (Owner == toinsert)
return false;
// no, you can't put maps or grids into containers
if (toinsert.HasComponent<IMapComponent>() || toinsert.HasComponent<IMapGridComponent>())
return false;
// Crucial, prevent circular insertion.
return !toinsert.Transform.ContainsEntity(Owner.Transform);
@@ -165,19 +96,13 @@ namespace Robust.Server.GameObjects
public bool Remove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
if (toremove == null)
return true;
if (!CanRemove(toremove))
{
return false;
}
if (!CanRemove(toremove)) return false;
InternalRemove(toremove);
if (!toremove.IsValid())
return true;
toremove.Transform.AttachParentToContainerOrGrid();
return true;
}
@@ -186,27 +111,13 @@ namespace Robust.Server.GameObjects
public void ForceRemove(IEntity toRemove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toRemove);
DebugTools.Assert(toRemove.IsValid());
InternalRemove(toRemove);
}
/// <summary>
/// Implement to remove the reference you used to store the entity
/// </summary>
/// <param name="toremove"></param>
protected virtual void InternalRemove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
Owner?.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(toremove, this));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
Manager.Dirty();
}
/// <inheritdoc />
public virtual bool CanRemove(IEntity toremove)
{
@@ -223,39 +134,44 @@ namespace Robust.Server.GameObjects
Manager.InternalContainerShutdown(this);
Deleted = true;
}
}
/// <summary>
/// The contents of this container have been changed.
/// </summary>
public class ContainerContentsModifiedMessage : ComponentMessage
{
/// <summary>
/// Container whose contents were modified.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// Entity that was added or removed from the container.
/// Implement to store the reference in whatever form you want
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// If true, the entity was removed. If false, it was added to the container.
/// </summary>
public bool Removed { get; }
/// <summary>
/// Constructs a new instance of <see cref="ContainerContentsModifiedMessage"/>.
/// </summary>
/// <param name="container">Container whose contents were modified.</param>
/// <param name="entity">Entity that was added or removed in the container.</param>
/// <param name="removed">If true, the entity was removed. If false, it was added to the container.</param>
public ContainerContentsModifiedMessage(IContainer container, IEntity entity, bool removed)
/// <param name="toinsert"></param>
protected virtual void InternalInsert(IEntity toinsert)
{
Container = container;
Entity = entity;
Removed = removed;
DebugTools.Assert(!Deleted);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(toinsert, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toinsert));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false));
Manager.Dirty();
}
/// <summary>
/// Implement to remove the reference you used to store the entity
/// </summary>
/// <param name="toremove"></param>
protected virtual void InternalRemove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(toremove, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toremove));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
Manager.Dirty();
}
/// <inheritdoc />
public virtual void ExposeData(ObjectSerializer serializer)
{
// ID and Manager are filled in Initialize
serializer.DataReadWriteFunction("showEnts", false, value => ShowContents = value, () => ShowContents);
serializer.DataReadWriteFunction("occludes", true, value => OccludesLight = value, () => OccludesLight);
}
}
}

View File

@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Robust.Shared.Containers
{
/// <summary>
/// Default implementation for containers,
/// cannot be inherited. If additional logic is needed,
/// this logic should go on the systems that are holding this container.
/// For example, inventory containers should be modified only through an inventory component.
/// </summary>
[UsedImplicitly]
[SerializedType(ClassName)]
public sealed class Container : BaseContainer
{
private const string ClassName = "Container";
/// <summary>
/// The generic container class uses a list of entities
/// </summary>
private List<IEntity> _containerList = new();
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
#if SERV3
// ONLY PAUL CAN MAKE ME WHOLE
serializer.DataField(ref _containerList, "ents", new List<IEntity>());
#else
if (serializer.Writing)
{
serializer.DataWriteFunction("ents", new List<EntityUid>(),
() => _containerList.Select(e => e.Uid).ToList());
}
else
{
var entMan = IoCManager.Resolve<IEntityManager>();
serializer.DataReadFunction("ents", new List<EntityUid>(),
value => _containerList = value.Select((uid => entMan.GetEntity(uid))).ToList());
}
#endif
}
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
_containerList.Add(toinsert);
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
_containerList.Remove(toremove);
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
return _containerList.Contains(contained);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
foreach (var entity in _containerList)
{
entity.Delete();
}
}
}
}

View File

@@ -0,0 +1,38 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// The contents of this container have been changed.
/// </summary>
public class ContainerContentsModifiedMessage : ComponentMessage
{
/// <summary>
/// Container whose contents were modified.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// Entity that was added or removed from the container.
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// If true, the entity was removed. If false, it was added to the container.
/// </summary>
public bool Removed { get; }
/// <summary>
/// Constructs a new instance of <see cref="ContainerContentsModifiedMessage"/>.
/// </summary>
/// <param name="container">Container whose contents were modified.</param>
/// <param name="entity">Entity that was added or removed in the container.</param>
/// <param name="removed">If true, the entity was removed. If false, it was added to the container.</param>
public ContainerContentsModifiedMessage(IContainer container, IEntity entity, bool removed)
{
Container = container;
Entity = entity;
Removed = removed;
}
}
}

View File

@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
@@ -9,6 +11,7 @@ namespace Robust.Shared.Containers
/// <summary>
/// Helper functions for the container system.
/// </summary>
[PublicAPI]
public static class ContainerHelpers
{
/// <summary>
@@ -23,9 +26,11 @@ namespace Robust.Shared.Containers
// Notice the recursion starts at the Owner of the passed in entity, this
// allows containers inside containers (toolboxes in lockers).
if (entity.Transform.Parent != null)
if (TryGetManagerComp(entity.Transform.Parent.Owner, out var containerComp))
return containerComp.ContainsEntity(entity);
if (entity.Transform.Parent == null)
return false;
if (TryGetManagerComp(entity.Transform.Parent.Owner, out var containerComp))
return containerComp.ContainsEntity(entity);
return false;
}
@@ -41,7 +46,8 @@ namespace Robust.Shared.Containers
DebugTools.AssertNotNull(entity);
DebugTools.Assert(!entity.Deleted);
if (entity.Transform.Parent != null && TryGetManagerComp(entity.Transform.Parent.Owner, out manager) && manager.ContainsEntity(entity))
var parentTransform = entity.Transform.Parent;
if (parentTransform != null && TryGetManagerComp(parentTransform.Owner, out manager) && manager.ContainsEntity(entity))
return true;
manager = default;
@@ -67,7 +73,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
@@ -87,7 +93,6 @@ namespace Robust.Shared.Containers
container.ForceRemove(entity);
return true;
}
wasInContainer = false;
@@ -95,7 +100,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
@@ -106,7 +111,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove all entities in a container.
/// Attempts to remove all entities in a container.
/// </summary>
public static void EmptyContainer(this IContainer container, bool force = false, EntityCoordinates? moveTo = null, bool attachToGridOrMap = false)
{
@@ -128,7 +133,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove and delete all entities in a container.
/// Attempts to remove and delete all entities in a container.
/// </summary>
public static void CleanContainer(this IContainer container)
{
@@ -145,23 +150,16 @@ namespace Robust.Shared.Containers
if (transform.Parent == null
|| !TryGetContainer(transform.Parent.Owner, out var container)
|| !TryInsertIntoContainer(transform, container))
{
transform.AttachToGridOrMap();
}
}
private static bool TryInsertIntoContainer(this ITransformComponent transform, IContainer container)
{
if (container.Insert(transform.Owner))
{
return true;
}
if (container.Insert(transform.Owner)) return true;
if (container.Owner.Transform.Parent != null
&& TryGetContainer(container.Owner, out var newContainer))
{
return TryInsertIntoContainer(transform, newContainer);
}
return false;
}
@@ -190,19 +188,58 @@ namespace Robust.Shared.Containers
var isOtherContained = TryGetContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained)
{
return true;
}
if (!isUserContained && !isOtherContained) return true;
// Both entities are in different contained states
if (isUserContained != isOtherContained)
{
return false;
}
if (isUserContained != isOtherContained) return false;
// Both entities are in the same container
return userContainer == otherContainer;
}
/// <summary>
/// Shortcut method to make creation of containers easier.
/// Creates a new container on the entity and gives it back to you.
/// </summary>
/// <param name="entity">The entity to create the container for.</param>
/// <param name="containerId"></param>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
/// <seealso cref="IContainerManager.MakeContainer{T}(string)" />
public static T CreateContainer<T>(this IEntity entity, string containerId)
where T : IContainer
{
if (!entity.TryGetComponent<IContainerManager>(out var containermanager))
containermanager = entity.AddComponent<ContainerManagerComponent>();
return containermanager.MakeContainer<T>(containerId);
}
public static T EnsureContainer<T>(this IEntity entity, string containerId)
where T : IContainer
{
return EnsureContainer<T>(entity, containerId, out _);
}
public static T EnsureContainer<T>(this IEntity entity, string containerId, out bool alreadyExisted)
where T : IContainer
{
var containerManager = entity.EnsureComponent<ContainerManagerComponent>();
if (!containerManager.TryGetContainer(containerId, out var existing))
{
alreadyExisted = false;
return containerManager.MakeContainer<T>(containerId);
}
if (!(existing is T container))
{
throw new InvalidOperationException(
$"The container exists but is of a different type: {existing.GetType()}");
}
alreadyExisted = true;
return container;
}
}
}

View File

@@ -0,0 +1,402 @@
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.ViewVariables;
namespace Robust.Shared.Containers
{
/// <summary>
/// Holds data about a set of entity containers on this entity.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IContainerManager))]
public class ContainerManagerComponent : Component, IContainerManager
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[ViewVariables] private Dictionary<string, IContainer> _containers = new();
/// <inheritdoc />
public sealed override string Name => "ContainerContainer";
/// <inheritdoc />
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
/// <inheritdoc />
public override void OnRemove()
{
base.OnRemove();
// IContianer.Shutdown modifies the _containers collection
foreach (var container in _containers.Values.ToArray())
{
container.Shutdown();
}
_containers.Clear();
}
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
foreach (var container in _containers)
{
var baseContainer = (BaseContainer)container.Value;
baseContainer.Manager = this;
baseContainer.ID = container.Key;
}
}
/// <inheritdoc />
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (!(curState is ContainerManagerComponentState cast))
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in _containers)
{
if (!cast.ContainerSet.Any(data => data.Id == id))
{
container.Shutdown();
toDelete ??= new List<string>();
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<IEntity>? toRemove = null;
foreach (var entity in container.ContainedEntities)
{
if (!entityUids.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
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<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = this;
return newContainer;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _containers, "containers", new Dictionary<string, IContainer>());
}
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)
{
// naive implementation that just sends the full state of the component
List<ContainerManagerComponentState.ContainerData> 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);
}
/// <inheritdoc />
public T MakeContainer<T>(string id)
where T : IContainer
{
return (T) MakeContainer(id, typeof(T));
}
/// <inheritdoc />
public IContainer GetContainer(string id)
{
return _containers[id];
}
/// <inheritdoc />
public bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
/// <inheritdoc />
public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity)) return true;
}
return false;
}
/// <inheritdoc />
public void ForceRemove(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (container.Contains(entity)) container.ForceRemove(entity);
}
}
/// <inheritdoc />
public void InternalContainerShutdown(IContainer container)
{
_containers.Remove(container.ID);
}
/// <inheritdoc />
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
}
/// <inheritdoc />
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<BaseContainer>(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<ContainerData> ContainerSet;
public ContainerManagerComponentState(List<ContainerData> 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;
}
}
}
private struct ContainerPrototypeData : IExposeData
{
public List<EntityUid> Entities;
public string? Type;
public ContainerPrototypeData(List<EntityUid> entities, string type)
{
Entities = entities;
Type = type;
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Entities, "entities", new List<EntityUid>());
serializer.DataField(ref Type, "type", null);
}
}
public readonly struct AllContainersEnumerable : IEnumerable<IContainer>
{
private readonly ContainerManagerComponent _manager;
public AllContainersEnumerable(ContainerManagerComponent manager)
{
_manager = manager;
}
public AllContainersEnumerator GetEnumerator()
{
return new(_manager);
}
IEnumerator<IContainer> IEnumerable<IContainer>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public struct AllContainersEnumerator : IEnumerator<IContainer>
{
private Dictionary<string, IContainer>.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<IContainer>) _enumerator).Reset();
}
[AllowNull]
public IContainer Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose() { }
}
}
}

View File

@@ -0,0 +1,28 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// Raised when the contents of a container have been modified.
/// </summary>
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
{
/// <summary>
/// The container being acted upon.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// The entity that was removed or inserted from/into the container.
/// </summary>
public IEntity Entity { get; }
protected ContainerModifiedMessage(IEntity entity, IContainer container)
{
Entity = entity;
Container = container;
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Containers
{
[UsedImplicitly]
[SerializedType(ClassName)]
public class ContainerSlot : BaseContainer
{
private const string ClassName = "ContainerSlot";
private IEntity? _containedEntity;
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities
{
get
{
if (ContainedEntity == null) return Array.Empty<IEntity>();
return new List<IEntity> {ContainedEntity};
}
}
[ViewVariables]
public IEntity? ContainedEntity
{
get => _containedEntity;
private set => _containedEntity = value;
}
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
#if SERV3
// ONLY PAUL CAN MAKE ME WHOLE
serializer.DataField(ref _containedEntity, "ent", default);
#else
if (serializer.Writing)
{
serializer.DataWriteFunction("ents", EntityUid.Invalid,
() => _containedEntity?.Uid ?? EntityUid.Invalid);
}
else
{
var entMan = IoCManager.Resolve<IEntityManager>();
serializer.DataReadFunction("ent", EntityUid.Invalid,
value => _containedEntity = value != EntityUid.Invalid ? entMan.GetEntity(value) : null);
}
#endif
}
/// <inheritdoc />
public override bool CanInsert(IEntity toinsert)
{
if (ContainedEntity != null)
return false;
return base.CanInsert(toinsert);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
if (contained == ContainedEntity)
return true;
return false;
}
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
ContainedEntity = toinsert;
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
ContainedEntity = null;
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
ContainedEntity?.Delete();
}
}
}

View File

@@ -1,9 +1,10 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
namespace Robust.Shared.Containers
{
internal sealed class ContainerSystem : EntitySystem
public class ContainerSystem : EntitySystem
{
/// <inheritdoc />
public override void Initialize()
{
SubscribeLocalEvent<EntParentChangedMessage>(HandleParentChanged);
@@ -18,9 +19,7 @@ namespace Robust.Server.GameObjects
return;
if (oldParentEntity.TryGetComponent(out IContainerManager? containerManager))
{
containerManager.ForceRemove(message.Entity);
}
}
}
}

View File

@@ -0,0 +1,14 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// Raised when an entity is inserted into a container.
/// </summary>
[PublicAPI]
public sealed class EntInsertedIntoContainerMessage : ContainerModifiedMessage
{
public EntInsertedIntoContainerMessage(IEntity entity, IContainer container) : base(entity, container) { }
}
}

View File

@@ -0,0 +1,14 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// Raised when an entity is removed from a container.
/// </summary>
[PublicAPI]
public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage
{
public EntRemovedFromContainerMessage(IEntity entity, IContainer container) : base(entity, container) { }
}
}

View File

@@ -1,42 +1,41 @@
using System;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// A container is a way to "contain" entities inside other entities, in a logical way.
/// This is alike BYOND's <c>contents</c> system, except more advanced.
/// </summary>
/// <remarks>
/// <p>
/// 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.
/// </p>
/// <p>
/// Containers are managed by an entity's <see cref="IContainerManager" />,
/// and have an ID to be referenced by.
/// </p>
/// <p>
/// 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.
/// </p>
/// <p>
/// Containers are managed by an entity's <see cref="IContainerManager" />,
/// and have an ID to be referenced by.
/// </p>
/// </remarks>
/// <seealso cref="IContainerManager" />
public interface IContainer
[PublicAPI]
public interface IContainer : IExposeData
{
/// <summary>
/// The container manager owning this container.
/// Readonly collection of all the entities contained within this specific container
/// </summary>
IContainerManager Manager { get; }
IReadOnlyList<IEntity> ContainedEntities { get; }
/// <summary>
/// The ID of this container.
/// The type of this container.
/// </summary>
string ID { get; }
/// <summary>
/// The entity owning this container.
/// </summary>
IEntity Owner { get; }
string ContainerType { get; }
/// <summary>
/// True if the container has been shut down via <see cref="Shutdown" />
@@ -44,16 +43,30 @@ namespace Robust.Shared.GameObjects
bool Deleted { get; }
/// <summary>
/// Readonly collection of all the entities contained within this specific container
/// The ID of this container.
/// </summary>
IReadOnlyList<IEntity> ContainedEntities { get; }
string ID { get; }
/// <summary>
/// The container manager owning this container.
/// </summary>
IContainerManager Manager { get; }
/// <summary>
/// Prevents light from escaping the container, from ex. a flashlight.
/// </summary>
bool OccludesLight { get; set; }
/// <summary>
/// The entity owning this container.
/// </summary>
IEntity Owner { get; }
/// <summary>
/// Should the contents of this container be shown? False for closed containers like lockers, true for
/// things like glass display cases.
/// </summary>
bool ShowContents { get; set; }
bool OccludesLight { get; set; }
/// <summary>
/// Checks if the entity can be inserted into this container.
@@ -91,6 +104,11 @@ namespace Robust.Shared.GameObjects
/// <returns>True if the entity was removed, false otherwise.</returns>
bool Remove(IEntity toremove);
/// <summary>
/// Forcefully removes an entity from the container. Normally you would want to use <see cref="Remove" />,
/// this function should be avoided.
/// </summary>
/// <param name="toRemove">The entity to attempt to remove.</param>
void ForceRemove(IEntity toRemove);
/// <summary>

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// Manages containers on an entity.
@@ -17,7 +18,8 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">The type of the new container</typeparam>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID</exception>
T MakeContainer<T>(string id) where T: IContainer;
T MakeContainer<T>(string id)
where T : IContainer;
/// <summary>
/// Attempts to remove <paramref name="entity" /> contained inside the owning entity,
@@ -32,7 +34,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="id">The ID to look up.</param>
/// <returns>The container.</returns>
/// <exception cref="KeyNotFoundException" >Thrown if the container does not exist.</exception>
/// <exception cref="KeyNotFoundException">Thrown if the container does not exist.</exception>
IContainer GetContainer(string id);
/// <summary>
@@ -64,7 +66,7 @@ namespace Robust.Shared.GameObjects
void ForceRemove(IEntity entity);
/// <summary>
/// DO NOT CALL THIS DIRECTLY. Call <see cref="IContainer.Shutdown"/> instead.
/// DO NOT CALL THIS DIRECTLY. Call <see cref="IContainer.Shutdown" /> instead.
/// </summary>
void InternalContainerShutdown(IContainer container);
}

View File

@@ -0,0 +1,14 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
public readonly struct UpdateContainerOcclusionMessage
{
public IEntity Entity { get; }
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
public abstract class SharedContainerManagerComponent : Component, IContainerManager
{
public sealed override string Name => "ContainerContainer";
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
public abstract T MakeContainer<T>(string id) where T : IContainer;
public abstract bool Remove(IEntity entity);
public abstract IContainer GetContainer(string id);
public abstract bool HasContainer(string id);
public abstract bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container);
/// <inheritdoc />
public abstract bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container);
public abstract bool ContainsEntity(IEntity entity);
public abstract void ForceRemove(IEntity entity);
public abstract void InternalContainerShutdown(IContainer container);
[Serializable, NetSerializable]
protected class ContainerManagerComponentState : ComponentState
{
public Dictionary<string, ContainerData> Containers { get; }
public ContainerManagerComponentState(Dictionary<string, ContainerData> containers) : base(NetIDs.CONTAINER_MANAGER)
{
Containers = containers;
}
[Serializable, NetSerializable]
public struct ContainerData
{
public bool ShowContents;
public bool OccludesLight;
public EntityUid[] ContainedEntities;
}
}
public IEnumerable<IContainer> GetAllContainers() => GetAllContainersImpl();
// Separate impl method to facilitate method hiding in the subclasses.
protected abstract IEnumerable<IContainer> GetAllContainersImpl();
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Robust.Shared.Network;
@@ -37,6 +38,14 @@ namespace Robust.Shared.Serialization
object Deserialize(Stream stream);
bool CanSerialize(Type type);
/// <summary>
/// Searches for a type with a given SerializedName that can be assigned to another type.
/// </summary>
/// <param name="assignableType">object type that it can be assigned to, like a base class or interface.</param>
/// <param name="serializedTypeName">The serializedName inside the <see cref="NetSerializableAttribute"/>.</param>
/// <returns>Type found, if any.</returns>
Type? FindSerializedType(Type assignableType, string serializedTypeName);
Task Handshake(INetChannel sender);
event Action ClientHandshakeComplete;

View File

@@ -1,9 +1,10 @@
using NetSerializer;
using NetSerializer;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
@@ -182,6 +183,23 @@ namespace Robust.Shared.Serialization
public bool CanSerialize(Type type)
=> _serializableTypes.Contains(type);
/// <inheritdoc />
public Type? FindSerializedType(Type assignableType, string serializedTypeName)
{
var types = _reflectionManager.GetAllChildren(assignableType);
foreach (var type in types)
{
var serializedAttribute = type.GetCustomAttribute<SerializedTypeAttribute>();
if(serializedAttribute is null)
continue;
if (serializedAttribute.SerializeName == serializedTypeName)
return type;
}
return null;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -786,7 +786,7 @@ namespace Robust.Shared.Serialization
var valNode = TypeToNode(entry.Value);
// write the concrete type tag
AssignTag<object?>(valType, entry, null, valNode);
AssignTag<object?>(valType, entry.Value, null, valNode);
node.Add(keyNode, valNode);
}

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
@@ -91,8 +92,13 @@ namespace Robust.Shared.Timing
public bool IsGridPaused(GridId gridId)
{
var grid = _mapManager.GetGrid(gridId);
return IsGridPaused(grid);
if (_mapManager.TryGetGrid(gridId, out var grid))
{
return IsGridPaused(grid);
}
Logger.ErrorS("map", $"Tried to check if unknown grid {gridId} was paused.");
return true;
}
public bool IsMapInitialized(MapId mapId)

View File

@@ -1,51 +1,46 @@
using System.Collections.Generic;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
namespace Robust.UnitTesting.Server.GameObjects.Components
{
[TestFixture]
public class ContainerTest : RobustUnitTest
[TestFixture, Parallelizable]
public class ContainerTest
{
private IServerEntityManager EntityManager = default!;
private static ISimulation SimulationFactory()
{
var sim = RobustServerSimulation
.NewSimulation()
.RegisterComponents(factory => { factory.RegisterClass<ContainerManagerComponent>(); })
.RegisterPrototypes(protoMan => protoMan.LoadString(PROTOTYPES))
.InitializeInstance();
// Adds the map with id 1, and spawns entity 1 as the map entity.
sim.AddMap(1);
return sim;
}
const string PROTOTYPES = @"
- type: entity
name: dummy
id: dummy
components:
- type: Transform
";
[OneTimeSetUp]
public void Setup()
{
var compMan = IoCManager.Resolve<IComponentManager>();
compMan.Initialize();
EntityManager = IoCManager.Resolve<IServerEntityManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
mapManager.Initialize();
mapManager.Startup();
mapManager.CreateMap();
var manager = IoCManager.Resolve<IPrototypeManager>();
manager.LoadFromStream(new StringReader(PROTOTYPES));
manager.Resync();
}
[Test]
public void TestCreation()
{
var entity = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var sim = SimulationFactory();
var container = ContainerManagerComponent.Create<Container>("dummy", entity);
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(entity, "dummy");
Assert.That(container.ID, Is.EqualTo("dummy"));
Assert.That(container.Owner, Is.EqualTo(entity));
@@ -53,10 +48,10 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
var manager = entity.GetComponent<IContainerManager>();
Assert.That(container.Manager, Is.EqualTo(manager));
Assert.That(() => ContainerManagerComponent.Create<Container>("dummy", entity), Throws.ArgumentException);
Assert.That(() => ContainerHelpers.CreateContainer<Container>(entity, "dummy"), Throws.ArgumentException);
Assert.That(manager.HasContainer("dummy2"), Is.False);
var container2 = ContainerManagerComponent.Create<Container>("dummy2", entity);
var container2 = ContainerHelpers.CreateContainer<Container>(entity, "dummy2");
Assert.That(container2.Manager, Is.EqualTo(manager));
Assert.That(container2.Owner, Is.EqualTo(entity));
@@ -80,15 +75,17 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
[Test]
public void TestInsertion()
{
var owner = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var inserted = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var sim = SimulationFactory();
var owner = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var inserted = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var transform = inserted.Transform;
var container = ContainerManagerComponent.Create<Container>("dummy", owner);
var container = ContainerHelpers.CreateContainer<Container>(owner, "dummy");
Assert.That(container.Insert(inserted), Is.True);
Assert.That(transform.Parent!.Owner, Is.EqualTo(owner));
var container2 = ContainerManagerComponent.Create<Container>("dummy", inserted);
var container2 = ContainerHelpers.CreateContainer<Container>(inserted, "dummy");
Assert.That(container2.Insert(owner), Is.False);
var success = container.Remove(inserted);
@@ -106,16 +103,18 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
[Test]
public void TestNestedRemoval()
{
var owner = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var inserted = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var transform = inserted.Transform;
var entity = EntityManager.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var sim = SimulationFactory();
var container = ContainerManagerComponent.Create<Container>("dummy", owner);
var owner = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var inserted = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var transform = inserted.Transform;
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(owner, "dummy");
Assert.That(container.Insert(inserted), Is.True);
Assert.That(transform.Parent!.Owner, Is.EqualTo(owner));
var container2 = ContainerManagerComponent.Create<Container>("dummy", inserted);
var container2 = ContainerHelpers.CreateContainer<Container>(inserted, "dummy");
Assert.That(container2.Insert(entity), Is.True);
Assert.That(entity.Transform.Parent!.Owner, Is.EqualTo(inserted));
@@ -130,15 +129,17 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
[Test]
public void TestNestedRemovalWithDenial()
{
var coordinates = new EntityCoordinates(new EntityUid(1), (0, 0));
var entityOne = EntityManager.SpawnEntity("dummy", coordinates);
var entityTwo = EntityManager.SpawnEntity("dummy", coordinates);
var entityThree = EntityManager.SpawnEntity("dummy", coordinates);
var entityItem = EntityManager.SpawnEntity("dummy", coordinates);
var sim = SimulationFactory();
var container = ContainerManagerComponent.Create<Container>("dummy", entityOne);
var container2 = ContainerManagerComponent.Create<ContainerOnlyContainer>("dummy", entityTwo);
var container3 = ContainerManagerComponent.Create<Container>("dummy", entityThree);
var coordinates = new EntityCoordinates(new EntityUid(1), (0, 0));
var entityOne = sim.SpawnEntity("dummy", coordinates);
var entityTwo = sim.SpawnEntity("dummy", coordinates);
var entityThree = sim.SpawnEntity("dummy", coordinates);
var entityItem = sim.SpawnEntity("dummy", coordinates);
var container = ContainerHelpers.CreateContainer<Container>(entityOne, "dummy");
var container2 = ContainerHelpers.CreateContainer<ContainerOnlyContainer>(entityTwo, "dummy");
var container3 = ContainerHelpers.CreateContainer<Container>(entityThree, "dummy");
Assert.That(container.Insert(entityTwo), Is.True);
Assert.That(entityTwo.Transform.Parent!.Owner, Is.EqualTo(entityOne));
@@ -157,6 +158,125 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
Assert.That(entityTwo.Transform.Deleted, Is.True);
}
[Test]
public void BaseContainer_SelfInsert_False()
{
var sim = SimulationFactory();
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(entity, "dummy");
Assert.That(container.Insert(entity), Is.False);
Assert.That(container.CanInsert(entity), Is.False);
}
[Test]
public void BaseContainer_InsertMap_False()
{
var sim = SimulationFactory();
var mapEnt = sim.Resolve<IEntityManager>().GetEntity(new EntityUid(1));
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(entity, "dummy");
Assert.That(container.Insert(mapEnt), Is.False);
Assert.That(container.CanInsert(mapEnt), Is.False);
}
[Test]
public void BaseContainer_InsertGrid_False()
{
var sim = SimulationFactory();
var grid = sim.Resolve<IMapManager>().CreateGrid(new MapId(1));
var gridEntity = sim.Resolve<IEntityManager>().GetEntity(grid.GridEntityId);
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(entity, "dummy");
Assert.That(container.Insert(gridEntity), Is.False);
Assert.That(container.CanInsert(gridEntity), Is.False);
}
[Test]
public void BaseContainer_Insert_True()
{
var sim = SimulationFactory();
var containerEntity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(containerEntity, "dummy");
var insertEntity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var result = container.Insert(insertEntity);
Assert.That(result, Is.True);
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
Assert.That(containerEntity.Transform.ChildCount, Is.EqualTo(1));
Assert.That(containerEntity.Transform.ChildEntityUids.First(), Is.EqualTo(insertEntity.Uid));
result = insertEntity.TryGetContainerMan(out var resultContainerMan);
Assert.That(result, Is.True);
Assert.That(resultContainerMan, Is.EqualTo(container.Manager));
}
[Test]
public void BaseContainer_RemoveNotAdded_False()
{
var sim = SimulationFactory();
var containerEntity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(containerEntity, "dummy");
var insertEntity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var result = container.Remove(insertEntity);
Assert.That(result, Is.False);
}
[Test]
public void BaseContainer_Transfer_True()
{
var sim = SimulationFactory();
var entity1 = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container1 = ContainerHelpers.CreateContainer<Container>(entity1, "dummy");
var entity2 = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container2 = ContainerHelpers.CreateContainer<Container>(entity2, "dummy");
var transferEntity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
container1.Insert(transferEntity);
var result = container2.Insert(transferEntity);
Assert.That(result, Is.True);
Assert.That(container1.ContainedEntities.Count, Is.EqualTo(0));
Assert.That(container2.ContainedEntities.Count, Is.EqualTo(1));
}
[Test]
public void Container_Serialize()
{
var sim = SimulationFactory();
var entity = sim.SpawnEntity("dummy", new EntityCoordinates(new EntityUid(1), (0, 0)));
var container = ContainerHelpers.CreateContainer<Container>(entity, "dummy");
var childEnt = sim.SpawnEntity(null, new EntityCoordinates(new EntityUid(1), (0, 0)));
container.OccludesLight = true;
container.ShowContents = true;
container.Insert(childEnt);
var containerMan = entity.GetComponent<IContainerManager>();
var state = (ContainerManagerComponent.ContainerManagerComponentState)containerMan.GetComponentState(new Mock<ICommonSession>().Object);
Assert.That(state.NetID, Is.EqualTo(containerMan.NetID));
Assert.That(state.ContainerSet.Count, Is.EqualTo(1));
Assert.That(state.ContainerSet[0].Id, Is.EqualTo("dummy"));
Assert.That(state.ContainerSet[0].OccludesLight, Is.True);
Assert.That(state.ContainerSet[0].ShowContents, Is.True);
Assert.That(state.ContainerSet[0].ContainedEntities.Length, Is.EqualTo(1));
Assert.That(state.ContainerSet[0].ContainedEntities[0], Is.EqualTo(childEnt.Uid));
}
private class ContainerOnlyContainer : BaseContainer
{
/// <summary>
@@ -164,8 +284,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
/// </summary>
private readonly List<IEntity> _containerList = new();
/// <inheritdoc />
public ContainerOnlyContainer(string id, IContainerManager manager) : base(id, manager) { }
public override string ContainerType => nameof(ContainerOnlyContainer);
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;

View File

@@ -0,0 +1,216 @@
using JetBrains.Annotations;
using Moq;
using Robust.Server.GameObjects;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Robust.UnitTesting.Server
{
[PublicAPI]
internal interface ISimulationFactory
{
ISimulationFactory RegisterComponents(CompRegistrationDelegate factory);
ISimulationFactory RegisterDependencies(DiContainerDelegate factory);
ISimulationFactory RegisterEntitySystems(EntitySystemRegistrationDelegate factory);
ISimulationFactory RegisterPrototypes(PrototypeRegistrationDelegate factory);
ISimulation InitializeInstance();
}
[PublicAPI]
internal interface ISimulation
{
IDependencyCollection Collection { get; }
/// <summary>
/// Resolves a dependency directly out of IoC collection.
/// </summary>
T Resolve<T>();
/// <summary>
/// Adds a new map directly to the map manager.
/// </summary>
EntityUid AddMap(int mapId);
EntityUid AddMap(MapId mapId);
IEntity SpawnEntity(string? protoId, EntityCoordinates coordinates);
IEntity SpawnEntity(string? protoId, MapCoordinates coordinates);
}
internal delegate void DiContainerDelegate(IDependencyCollection diContainer);
internal delegate void CompRegistrationDelegate(IComponentFactory factory);
internal delegate void EntitySystemRegistrationDelegate(IEntitySystemManager systemMan);
internal delegate void PrototypeRegistrationDelegate(IPrototypeManager protoMan);
internal class RobustServerSimulation : ISimulation, ISimulationFactory
{
private DiContainerDelegate? _diFactory;
private CompRegistrationDelegate? _regDelegate;
private EntitySystemRegistrationDelegate? _systemDelegate;
private PrototypeRegistrationDelegate? _protoDelegate;
public IDependencyCollection Collection { get; private set; } = default!;
public T Resolve<T>()
{
return Collection.Resolve<T>();
}
public EntityUid AddMap(int mapId)
{
var mapMan = Collection.Resolve<IMapManager>();
mapMan.CreateMap(new MapId(mapId));
return mapMan.GetMapEntityId(new MapId(mapId));
}
public EntityUid AddMap(MapId mapId)
{
var mapMan = Collection.Resolve<IMapManager>();
mapMan.CreateMap(mapId);
return mapMan.GetMapEntityId(mapId);
}
public IEntity SpawnEntity(string? protoId, EntityCoordinates coordinates)
{
var entMan = Collection.Resolve<IEntityManager>();
return entMan.SpawnEntity(protoId, coordinates);
}
public IEntity SpawnEntity(string? protoId, MapCoordinates coordinates)
{
var entMan = Collection.Resolve<IEntityManager>();
return entMan.SpawnEntity(protoId, coordinates);
}
private RobustServerSimulation() { }
public ISimulationFactory RegisterDependencies(DiContainerDelegate factory)
{
_diFactory += factory;
return this;
}
public ISimulationFactory RegisterComponents(CompRegistrationDelegate factory)
{
_regDelegate += factory;
return this;
}
public ISimulationFactory RegisterEntitySystems(EntitySystemRegistrationDelegate factory)
{
_systemDelegate += factory;
return this;
}
public ISimulationFactory RegisterPrototypes(PrototypeRegistrationDelegate factory)
{
_protoDelegate += factory;
return this;
}
public ISimulation InitializeInstance()
{
var container = new DependencyCollection();
Collection = container;
IoCManager.InitThread(container, true);
//TODO: This is a long term project that should eventually have parity with the actual server/client/SP IoC registration.
// The goal is to be able to pull out all networking and frontend dependencies, and only have a core simulation running.
// This does NOT replace the full RobustIntegrationTest, or regular unit testing. This simulation sits in the middle
// and allows you to run integration testing only on the simulation.
//Tier 1: System
container.Register<ILogManager, LogManager>();
container.Register<IRuntimeLog, RuntimeLog>();
container.Register<IConfigurationManager, ConfigurationManager>();
container.Register<IDynamicTypeFactory, DynamicTypeFactory>();
container.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
container.Register<ILocalizationManager, LocalizationManager>();
container.Register<IModLoader, TestingModLoader>();
container.Register<IModLoaderInternal, TestingModLoader>();
container.RegisterInstance<ITaskManager>(new Mock<ITaskManager>().Object);
container.RegisterInstance<IReflectionManager>(new Mock<IReflectionManager>().Object); // tests should not be searching for types
container.RegisterInstance<IRobustSerializer>(new Mock<IRobustSerializer>().Object);
container.RegisterInstance<IResourceManager>(new Mock<IResourceManager>().Object); // no disk access for tests
container.RegisterInstance<IGameTiming>(new Mock<IGameTiming>().Object); // TODO: get timing working similar to RobustIntegrationTest
//Tier 2: Simulation
container.Register<IServerEntityManager, ServerEntityManager>();
container.Register<IEntityManager, ServerEntityManager>();
container.Register<IComponentManager, ComponentManager>();
container.Register<IMapManager, MapManager>();
container.Register<IPrototypeManager, PrototypeManager>();
container.Register<IComponentFactory, ComponentFactory>();
container.Register<IComponentDependencyManager, ComponentDependencyManager>();
container.Register<IEntitySystemManager, EntitySystemManager>();
container.Register<IPhysicsManager, PhysicsManager>();
container.RegisterInstance<IPauseManager>(new Mock<IPauseManager>().Object); // TODO: get timing working similar to RobustIntegrationTest
//Tier 3: Networking
//TODO: Try to remove these
container.RegisterInstance<IEntityNetworkManager>(new Mock<IEntityNetworkManager>().Object);
container.RegisterInstance<INetManager>(new Mock<INetManager>().Object);
_diFactory?.Invoke(container);
container.BuildGraph();
var logMan = container.Resolve<ILogManager>();
logMan.RootSawmill.AddHandler(new TestLogHandler("SIM"));
var compFactory = container.Resolve<IComponentFactory>();
compFactory.Register<MetaDataComponent>();
compFactory.RegisterReference<MetaDataComponent, IMetaDataComponent>();
compFactory.Register<TransformComponent>();
compFactory.RegisterReference<TransformComponent, ITransformComponent>();
compFactory.Register<MapComponent>();
compFactory.RegisterReference<MapComponent, IMapComponent>();
compFactory.Register<MapGridComponent>();
compFactory.RegisterReference<MapGridComponent, IMapGridComponent>();
compFactory.Register<PhysicsComponent>();
compFactory.RegisterReference<PhysicsComponent, IPhysicsComponent>();
_regDelegate?.Invoke(compFactory);
var entityMan = container.Resolve<IEntityManager>();
entityMan.Initialize();
_systemDelegate?.Invoke(container.Resolve<IEntitySystemManager>());
entityMan.Startup();
var mapManager = container.Resolve<IMapManager>();
mapManager.Initialize();
mapManager.Startup();
var protoMan = container.Resolve<IPrototypeManager>();
protoMan.RegisterType(typeof(EntityPrototype));
_protoDelegate?.Invoke(protoMan);
protoMan.Resync();
return this;
}
public static ISimulationFactory NewSimulation()
{
return new RobustServerSimulation();
}
}
}