mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Robust.Shared/Containers/Container.cs
Normal file
88
Robust.Shared/Containers/Container.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Robust.Shared/Containers/ContainerContentsModifiedMessage.cs
Normal file
38
Robust.Shared/Containers/ContainerContentsModifiedMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
402
Robust.Shared/Containers/ContainerManagerComponent.cs
Normal file
402
Robust.Shared/Containers/ContainerManagerComponent.cs
Normal 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() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Robust.Shared/Containers/ContainerModifiedMessage.cs
Normal file
28
Robust.Shared/Containers/ContainerModifiedMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Robust.Shared/Containers/ContainerSlot.cs
Normal file
103
Robust.Shared/Containers/ContainerSlot.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Robust.Shared/Containers/EntInsertedIntoContainerMessage.cs
Normal file
14
Robust.Shared/Containers/EntInsertedIntoContainerMessage.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
14
Robust.Shared/Containers/EntRemovedFromContainerMessage.cs
Normal file
14
Robust.Shared/Containers/EntRemovedFromContainerMessage.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
14
Robust.Shared/Containers/UpdateContainerOcclusionMessage.cs
Normal file
14
Robust.Shared/Containers/UpdateContainerOcclusionMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
216
Robust.UnitTesting/Server/RobustServerSimulation.cs
Normal file
216
Robust.UnitTesting/Server/RobustServerSimulation.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user