mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-06 17:58:05 +02:00
393 lines
17 KiB
C#
393 lines
17 KiB
C#
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Serialization.Manager.Attributes;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using Robust.Shared.Map.Components;
|
|
|
|
namespace Robust.Shared.Containers
|
|
{
|
|
/// <summary>
|
|
/// Base container class that all container inherit from.
|
|
/// </summary>
|
|
public abstract partial class BaseContainer : IContainer
|
|
{
|
|
/// <inheritdoc />
|
|
[ViewVariables]
|
|
public abstract IReadOnlyList<EntityUid> ContainedEntities { get; }
|
|
|
|
[ViewVariables]
|
|
public abstract List<EntityUid> ExpectedEntities { get; }
|
|
|
|
/// <inheritdoc />
|
|
public abstract string ContainerType { get; }
|
|
|
|
/// <inheritdoc />
|
|
[ViewVariables]
|
|
public bool Deleted { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
[ViewVariables]
|
|
public string ID { get; internal set; } = default!; // Make sure you set me in init
|
|
|
|
/// <inheritdoc />
|
|
public ContainerManagerComponent Manager { get; internal set; } = default!; // Make sure you set me in init
|
|
|
|
/// <inheritdoc />
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
[DataField("occludes")]
|
|
public bool OccludesLight { get; set; } = true;
|
|
|
|
/// <inheritdoc />
|
|
[ViewVariables]
|
|
public EntityUid Owner => Manager.Owner;
|
|
|
|
/// <inheritdoc />
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
[DataField("showEnts")]
|
|
public bool ShowContents { get; set; }
|
|
|
|
/// <summary>
|
|
/// DO NOT CALL THIS METHOD DIRECTLY!
|
|
/// You want <see cref="IContainerManager.MakeContainer{T}(string)" /> instead.
|
|
/// </summary>
|
|
protected BaseContainer() { }
|
|
|
|
/// <inheritdoc />
|
|
public bool Insert(
|
|
EntityUid toinsert,
|
|
IEntityManager? entMan = null,
|
|
TransformComponent? transform = null,
|
|
TransformComponent? ownerTransform = null,
|
|
MetaDataComponent? meta = null,
|
|
PhysicsComponent? physics = null,
|
|
bool force = false)
|
|
{
|
|
DebugTools.Assert(!Deleted);
|
|
DebugTools.Assert(transform == null || transform.Owner == toinsert);
|
|
DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner);
|
|
DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner);
|
|
DebugTools.Assert(physics == null || physics.Owner == toinsert);
|
|
DebugTools.Assert(!ExpectedEntities.Contains(toinsert));
|
|
IoCManager.Resolve(ref entMan);
|
|
|
|
//Verify we can insert into this container
|
|
if (!force && !CanInsert(toinsert, entMan))
|
|
return false;
|
|
|
|
var physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
|
|
var transformQuery = entMan.GetEntityQuery<TransformComponent>();
|
|
var jointQuery = entMan.GetEntityQuery<JointComponent>();
|
|
|
|
// ECS containers when
|
|
var physicsSys = entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
|
var jointSys = entMan.EntitySysManager.GetEntitySystem<SharedJointSystem>();
|
|
|
|
// Please somebody ecs containers
|
|
var lookupSys = entMan.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
|
|
var xformSys = entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
|
|
|
transform ??= transformQuery.GetComponent(toinsert);
|
|
meta ??= entMan.GetComponent<MetaDataComponent>(toinsert);
|
|
|
|
// remove from any old containers.
|
|
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
|
|
entMan.TryGetComponent(transform.ParentUid, out ContainerManagerComponent? oldManager) &&
|
|
oldManager.TryGetContainer(toinsert, out var oldContainer) &&
|
|
!oldContainer.Remove(toinsert, entMan, transform, meta, false, false))
|
|
{
|
|
// failed to remove from container --> cannot insert.
|
|
return false;
|
|
}
|
|
|
|
// Update metadata first, so that parent change events can check IsInContainer.
|
|
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
|
meta.Flags |= MetaDataFlags.InContainer;
|
|
|
|
// Remove the entity and any children from broadphases.
|
|
// This is done before changing can collide to avoid unecceary updates.
|
|
// TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice?
|
|
lookupSys.RemoveFromEntityTree(toinsert, transform);
|
|
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid());
|
|
|
|
// Avoid unnecessary broadphase updates while unanchoring, changing physics collision, and re-parenting.
|
|
var old = transform.Broadphase;
|
|
transform.Broadphase = BroadphaseData.Invalid;
|
|
|
|
// Unanchor the entity (without changing physics body types).
|
|
xformSys.Unanchor(toinsert, transform, false);
|
|
|
|
// Next, update physics. Note that this cannot just be done in the physics system via parent change events,
|
|
// because the insertion may not result in a parent change. This could alternatively be done via a
|
|
// got-inserted event, but really that event should run after the entity was actually inserted (so that
|
|
// parent/map have updated). But we are better of disabling collision before doing map/parent changes.
|
|
physicsQuery.Resolve(toinsert, ref physics, false);
|
|
RecursivelyUpdatePhysics(toinsert, transform, physics, physicsSys, physicsQuery, transformQuery);
|
|
|
|
// Attach to new parent
|
|
var oldParent = transform.ParentUid;
|
|
xformSys.SetCoordinates(toinsert, transform, new EntityCoordinates(Owner, Vector2.Zero), Angle.Zero);
|
|
transform.Broadphase = old;
|
|
|
|
// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
|
|
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
|
|
|
// Implementation specific insert logic
|
|
InternalInsert(toinsert, entMan);
|
|
|
|
// Update any relevant joint relays
|
|
// Can't be done above as the container flag isn't set yet.
|
|
RecursivelyUpdateJoints(toinsert, transform, jointSys, jointQuery, transformQuery);
|
|
|
|
// Raise container events (after re-parenting and internal remove).
|
|
entMan.EventBus.RaiseLocalEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, oldParent, this), true);
|
|
entMan.EventBus.RaiseLocalEvent(toinsert, new EntGotInsertedIntoContainerMessage(toinsert, this), true);
|
|
|
|
// The sheer number of asserts tells you about how little I trust container and parenting code.
|
|
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
|
DebugTools.Assert(!transform.Anchored);
|
|
DebugTools.Assert(transform.LocalPosition == Vector2.Zero);
|
|
DebugTools.Assert(transform.LocalRotation == Angle.Zero);
|
|
DebugTools.Assert(!physicsQuery.TryGetComponent(toinsert, out var phys) || (!phys.Awake && !phys.CanCollide));
|
|
|
|
entMan.Dirty(Manager);
|
|
return true;
|
|
}
|
|
|
|
internal void RecursivelyUpdatePhysics(
|
|
EntityUid uid,
|
|
TransformComponent xform,
|
|
PhysicsComponent? physics,
|
|
SharedPhysicsSystem physicsSys,
|
|
EntityQuery<PhysicsComponent> physicsQuery,
|
|
EntityQuery<TransformComponent> transformQuery)
|
|
{
|
|
if (physics != null)
|
|
{
|
|
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
|
|
// changes. This also ensures that the server doesn't have to send the physics comp state to every
|
|
// player for any entity inside of a container during init.
|
|
physicsSys.SetLinearVelocity(uid, Vector2.Zero, false, body: physics);
|
|
physicsSys.SetAngularVelocity(uid,0, false, body: physics);
|
|
physicsSys.SetCanCollide(uid, false, false, body: physics);
|
|
}
|
|
|
|
var enumerator = xform.ChildEnumerator;
|
|
|
|
while (enumerator.MoveNext(out var child))
|
|
{
|
|
var childXform = transformQuery.GetComponent(child.Value);
|
|
physicsQuery.TryGetComponent(child.Value, out var childPhysics);
|
|
RecursivelyUpdatePhysics(child.Value, childXform, childPhysics, physicsSys, physicsQuery, transformQuery);
|
|
}
|
|
}
|
|
|
|
internal void RecursivelyUpdateJoints(
|
|
EntityUid uid,
|
|
TransformComponent xform,
|
|
SharedJointSystem jointSys,
|
|
EntityQuery<JointComponent> jointQuery,
|
|
EntityQuery<TransformComponent> transformQuery)
|
|
{
|
|
if (jointQuery.TryGetComponent(uid, out var jointComp))
|
|
{
|
|
// TODO: This is going to be going up while joints going down, although these aren't too common
|
|
// in SS14 atm.
|
|
jointSys.RefreshRelay(uid, jointComp);
|
|
}
|
|
|
|
var enumerator = xform.ChildEnumerator;
|
|
|
|
while (enumerator.MoveNext(out var child))
|
|
{
|
|
var childXform = transformQuery.GetComponent(child.Value);
|
|
RecursivelyUpdateJoints(child.Value, childXform, jointSys, jointQuery, transformQuery);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null)
|
|
{
|
|
DebugTools.Assert(!Deleted);
|
|
|
|
// cannot insert into itself.
|
|
if (Owner == toinsert)
|
|
return false;
|
|
|
|
IoCManager.Resolve(ref entMan);
|
|
|
|
// no, you can't put maps or grids into containers
|
|
if (entMan.HasComponent<MapComponent>(toinsert) || entMan.HasComponent<MapGridComponent>(toinsert))
|
|
return false;
|
|
|
|
var xformSystem = entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
|
var xformQuery = entMan.GetEntityQuery<TransformComponent>();
|
|
|
|
// Crucial, prevent circular insertion.
|
|
if (xformSystem.ContainsEntity(xformQuery.GetComponent(toinsert), Owner, xformQuery))
|
|
return false;
|
|
|
|
//Improvement: Traverse the entire tree to make sure we are not creating a loop.
|
|
|
|
//raise events
|
|
var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(this, toinsert);
|
|
entMan.EventBus.RaiseLocalEvent(Owner, insertAttemptEvent, true);
|
|
if (insertAttemptEvent.Cancelled)
|
|
return false;
|
|
|
|
var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(this, toinsert);
|
|
entMan.EventBus.RaiseLocalEvent(toinsert, gettingInsertedAttemptEvent, true);
|
|
if (gettingInsertedAttemptEvent.Cancelled)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Remove(
|
|
EntityUid toRemove,
|
|
IEntityManager? entMan = null,
|
|
TransformComponent? xform = null,
|
|
MetaDataComponent? meta = null,
|
|
bool reparent = true,
|
|
bool force = false,
|
|
EntityCoordinates? destination = null,
|
|
Angle? localRotation = null)
|
|
{
|
|
IoCManager.Resolve(ref entMan);
|
|
DebugTools.Assert(!Deleted);
|
|
DebugTools.AssertNotNull(Manager);
|
|
DebugTools.Assert(entMan.EntityExists(toRemove));
|
|
DebugTools.Assert(xform == null || xform.Owner == toRemove);
|
|
DebugTools.Assert(meta == null || meta.Owner == toRemove);
|
|
|
|
xform ??= entMan.GetComponent<TransformComponent>(toRemove);
|
|
meta ??= entMan.GetComponent<MetaDataComponent>(toRemove);
|
|
|
|
if (!force && !CanRemove(toRemove, entMan))
|
|
return false;
|
|
|
|
if (force && !Contains(toRemove))
|
|
{
|
|
DebugTools.Assert("Attempted to force remove an entity that was never inside of the container.");
|
|
return false;
|
|
}
|
|
|
|
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent));
|
|
DebugTools.Assert(xform.Broadphase == null || !xform.Broadphase.Value.IsValid());
|
|
DebugTools.Assert(!xform.Anchored);
|
|
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0);
|
|
DebugTools.Assert(!entMan.TryGetComponent(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide));
|
|
|
|
// Unset flag (before parent change events are raised).
|
|
meta.Flags &= ~MetaDataFlags.InContainer;
|
|
|
|
// Implementation specific remove logic
|
|
InternalRemove(toRemove, entMan);
|
|
|
|
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0);
|
|
var oldParent = xform.ParentUid;
|
|
|
|
if (destination != null)
|
|
{
|
|
// Container ECS when.
|
|
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetCoordinates(toRemove, xform, destination.Value, localRotation);
|
|
}
|
|
else if (reparent)
|
|
{
|
|
// Container ECS when.
|
|
entMan.EntitySysManager.GetEntitySystem<SharedContainerSystem>().AttachParentToContainerOrGrid(xform);
|
|
if (localRotation != null)
|
|
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetLocalRotation(xform, localRotation.Value);
|
|
}
|
|
|
|
// Add to new broadphase
|
|
if (xform.ParentUid == oldParent // move event should already have handled it
|
|
&& xform.Broadphase == null) // broadphase explicitly invalid?
|
|
{
|
|
entMan.EntitySysManager.GetEntitySystem<EntityLookupSystem>().FindAndAddToEntityTree(toRemove, xform: xform);
|
|
}
|
|
|
|
if (entMan.TryGetComponent<JointComponent>(toRemove, out var jointComp))
|
|
{
|
|
entMan.System<SharedJointSystem>().RefreshRelay(toRemove, jointComp);
|
|
}
|
|
|
|
// Raise container events (after re-parenting and internal remove).
|
|
entMan.EventBus.RaiseLocalEvent(Owner, new EntRemovedFromContainerMessage(toRemove, this), true);
|
|
entMan.EventBus.RaiseLocalEvent(toRemove, new EntGotRemovedFromContainerMessage(toRemove, this), false);
|
|
|
|
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value));
|
|
|
|
entMan.Dirty(Manager);
|
|
return true;
|
|
}
|
|
|
|
[Obsolete("use force option in Remove()")]
|
|
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
|
|
=> Remove(toRemove, entMan, meta: meta, reparent: false, force: true);
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool CanRemove(EntityUid toRemove, IEntityManager? entMan = null)
|
|
{
|
|
DebugTools.Assert(!Deleted);
|
|
|
|
if (!Contains(toRemove))
|
|
return false;
|
|
|
|
IoCManager.Resolve(ref entMan);
|
|
|
|
//raise events
|
|
var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(this, toRemove);
|
|
entMan.EventBus.RaiseLocalEvent(Owner, removeAttemptEvent, true);
|
|
if (removeAttemptEvent.Cancelled)
|
|
return false;
|
|
|
|
var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(this, toRemove);
|
|
entMan.EventBus.RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true);
|
|
if (gettingRemovedAttemptEvent.Cancelled)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public abstract bool Contains(EntityUid contained);
|
|
|
|
/// <inheritdoc />
|
|
public void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null)
|
|
{
|
|
IoCManager.Resolve(ref entMan, ref netMan);
|
|
InternalShutdown(entMan, netMan.IsClient);
|
|
Manager.Containers.Remove(ID);
|
|
Deleted = true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected abstract void InternalShutdown(IEntityManager entMan, bool isClient);
|
|
|
|
/// <summary>
|
|
/// Implement to store the reference in whatever form you want
|
|
/// </summary>
|
|
/// <param name="toInsert"></param>
|
|
/// <param name="entMan"></param>
|
|
protected abstract void InternalInsert(EntityUid toInsert, IEntityManager entMan);
|
|
|
|
/// <summary>
|
|
/// Implement to remove the reference you used to store the entity
|
|
/// </summary>
|
|
/// <param name="toRemove"></param>
|
|
/// <param name="entMan"></param>
|
|
protected abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan);
|
|
}
|
|
}
|