diff --git a/Content.Client/Storage/Components/EntityStorageComponent.cs b/Content.Client/Storage/Components/EntityStorageComponent.cs
new file mode 100644
index 0000000000..65b510cf6f
--- /dev/null
+++ b/Content.Client/Storage/Components/EntityStorageComponent.cs
@@ -0,0 +1,10 @@
+using Content.Shared.Storage.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Client.Storage.Components;
+
+[RegisterComponent, ComponentReference(typeof(SharedEntityStorageComponent))]
+public sealed class EntityStorageComponent : SharedEntityStorageComponent
+{
+
+}
diff --git a/Content.Client/Storage/Systems/EntityStorageSystem.cs b/Content.Client/Storage/Systems/EntityStorageSystem.cs
new file mode 100644
index 0000000000..36532b0e9f
--- /dev/null
+++ b/Content.Client/Storage/Systems/EntityStorageSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Storage.EntitySystems;
+
+namespace Content.Client.Storage.Systems;
+
+public sealed class EntityStorageSystem : SharedEntityStorageSystem
+{
+
+}
diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs
index 705ad00ab9..568c67bc06 100644
--- a/Content.Server/Storage/Components/EntityStorageComponent.cs
+++ b/Content.Server/Storage/Components/EntityStorageComponent.cs
@@ -1,97 +1,12 @@
using Content.Server.Atmos;
-using Content.Shared.Physics;
-using Content.Shared.Whitelist;
-using Robust.Shared.Audio;
-using Robust.Shared.Containers;
+using Content.Shared.Storage.Components;
+using Robust.Shared.GameStates;
namespace Content.Server.Storage.Components;
-[RegisterComponent]
-public sealed class EntityStorageComponent : Component, IGasMixtureHolder
+[RegisterComponent, ComponentReference(typeof(SharedEntityStorageComponent))]
+public sealed class EntityStorageComponent : SharedEntityStorageComponent, IGasMixtureHolder
{
- public readonly float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage.
- public const float GasMixVolume = 70f;
-
- public static readonly TimeSpan InternalOpenAttemptDelay = TimeSpan.FromSeconds(0.5);
- public TimeSpan LastInternalOpenAttempt;
-
- ///
- /// Collision masks that get removed when the storage gets opened.
- ///
- public readonly int MasksToRemove = (int) (
- CollisionGroup.MidImpassable |
- CollisionGroup.HighImpassable |
- CollisionGroup.LowImpassable);
-
- ///
- /// Collision masks that were removed from ANY layer when the storage was opened;
- ///
- [DataField("removedMasks")]
- public int RemovedMasks;
-
- [DataField("capacity")]
- public int Capacity = 30;
-
- [DataField("isCollidableWhenOpen")]
- public bool IsCollidableWhenOpen;
-
- ///
- /// If true, it opens the storage when the entity inside of it moves
- /// If false, it prevents the storage from opening when the entity inside of it moves.
- /// This is for objects that you want the player to move while inside, like large cardboard boxes, without opening the storage.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("openOnMove")]
- public bool OpenOnMove = true;
-
- //The offset for where items are emptied/vacuumed for the EntityStorage.
- [DataField("enteringOffset")]
- public Vector2 EnteringOffset = new(0, 0);
-
- //The collision groups checked, so that items are depositied or grabbed from inside walls.
- [DataField("enteringOffsetCollisionFlags")]
- public readonly CollisionGroup EnteringOffsetCollisionFlags = CollisionGroup.Impassable | CollisionGroup.MidImpassable;
-
- [DataField("enteringRange")]
- public float EnteringRange = 0.18f;
-
- [DataField("showContents")]
- public bool ShowContents;
-
- [DataField("occludesLight")]
- public bool OccludesLight = true;
-
- [DataField("deleteContentsOnDestruction"), ViewVariables(VVAccess.ReadWrite)]
- public bool DeleteContentsOnDestruction = false;
-
- ///
- /// Whether or not the container is sealed and traps air inside of it
- ///
- [DataField("airtight"), ViewVariables(VVAccess.ReadWrite)]
- public bool Airtight = true;
-
- [DataField("open")]
- public bool Open;
-
- [DataField("closeSound")]
- public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/closetclose.ogg");
-
- [DataField("openSound")]
- public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg");
-
- ///
- /// Whitelist for what entities are allowed to be inserted into this container. If this is not null, the
- /// standard requirement that the entity must be an item or mob is waived.
- ///
- [DataField("whitelist")]
- public EntityWhitelist? Whitelist;
-
- [ViewVariables]
- public Container Contents = default!;
-
- [ViewVariables(VVAccess.ReadWrite)]
- public bool IsWeldedShut;
-
///
/// Gas currently contained in this entity storage.
/// None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.
diff --git a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs
index 3d2bb019cc..ebefd304f7 100644
--- a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs
+++ b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs
@@ -1,93 +1,50 @@
-using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction;
using Content.Server.Construction.Components;
-using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Tools.Systems;
-using Content.Shared.Body.Components;
-using Content.Shared.Destructible;
-using Content.Shared.Hands.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Item;
-using Content.Shared.Lock;
-using Content.Shared.Placeable;
-using Content.Shared.Storage;
using Content.Shared.Storage.Components;
-using Content.Shared.Wall;
-using Content.Shared.Whitelist;
-using Robust.Server.Containers;
+using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Systems;
namespace Content.Server.Storage.EntitySystems;
-public sealed class EntityStorageSystem : EntitySystem
+public sealed class EntityStorageSystem : SharedEntityStorageSystem
{
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ConstructionSystem _construction = default!;
- [Dependency] private readonly ContainerSystem _container = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- [Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IMapManager _map = default!;
- public const string ContainerName = "entity_storage";
-
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnInit);
- SubscribeLocalEvent(OnInteract);
SubscribeLocalEvent(OnWeldableAttempt);
SubscribeLocalEvent(OnWelded);
- SubscribeLocalEvent(OnLockToggleAttempt);
- SubscribeLocalEvent(OnDestruction);
- SubscribeLocalEvent(OnRemoved);
SubscribeLocalEvent(OnInsideInhale);
SubscribeLocalEvent(OnInsideExhale);
SubscribeLocalEvent(OnInsideExposed);
+ SubscribeLocalEvent(OnRemoved);
}
- private void OnInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
+ protected override void OnInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
{
- component.Contents = _container.EnsureContainer(uid, ContainerName);
- component.Contents.ShowContents = component.ShowContents;
- component.Contents.OccludesLight = component.OccludesLight;
+ base.OnInit(uid, component, args);
if (TryComp(uid, out var construction))
_construction.AddContainer(uid, ContainerName, construction);
- if (TryComp(uid, out var placeable))
- _placeableSurface.SetPlaceable(uid, component.Open, placeable);
-
if (!component.Open)
{
// If we're closed on spawn, we need to pull some air into our environment from where we spawned,
// so that we have -something-. For example, if you bought an animal crate or something.
- TakeGas(uid, component);
+ TakeGas(uid, (EntityStorageComponent) component);
}
}
- private void OnInteract(EntityUid uid, EntityStorageComponent component, ActivateInWorldEvent args)
- {
- if (args.Handled)
- return;
-
- args.Handled = true;
- ToggleOpen(args.User, uid, component);
- }
-
private void OnWeldableAttempt(EntityUid uid, EntityStorageComponent component, WeldableAttemptEvent args)
{
if (component.Open)
@@ -99,7 +56,7 @@ public sealed class EntityStorageSystem : EntitySystem
if (component.Contents.Contains(args.User))
{
var msg = Loc.GetString("entity-storage-component-already-contains-user-message");
- _popupSystem.PopupEntity(msg, args.User, args.User);
+ Popup.PopupEntity(msg, args.User, args.User);
args.Cancel();
}
}
@@ -107,334 +64,36 @@ public sealed class EntityStorageSystem : EntitySystem
private void OnWelded(EntityUid uid, EntityStorageComponent component, WeldableChangedEvent args)
{
component.IsWeldedShut = args.IsWelded;
+ Dirty(component);
}
- private void OnLockToggleAttempt(EntityUid uid, EntityStorageComponent target, ref LockToggleAttemptEvent args)
- {
- // Cannot (un)lock open lockers.
- if (target.Open)
- args.Cancelled = true;
-
- // Cannot (un)lock from the inside. Maybe a bad idea? Security jocks could trap nerds in lockers?
- if (target.Contents.Contains(args.User))
- args.Cancelled = true;
- }
-
- private void OnDestruction(EntityUid uid, EntityStorageComponent component, DestructionEventArgs args)
- {
- component.Open = true;
- if (!component.DeleteContentsOnDestruction)
- {
- EmptyContents(uid, component);
- return;
- }
-
- foreach (var ent in new List(component.Contents.ContainedEntities))
- {
- EntityManager.DeleteEntity(ent);
- }
- }
-
- public void ToggleOpen(EntityUid user, EntityUid target, EntityStorageComponent? component = null)
- {
- if (!Resolve(target, ref component))
- return;
-
- if (component.Open)
- {
- TryCloseStorage(target);
- }
- else
- {
- TryOpenStorage(user, target);
- }
- }
-
- public void EmptyContents(EntityUid uid, EntityStorageComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- var uidXform = Transform(uid);
- var containedArr = component.Contents.ContainedEntities.ToArray();
- foreach (var contained in containedArr)
- {
- Remove(contained, uid, component, uidXform);
- }
- }
-
- public void OpenStorage(EntityUid uid, EntityStorageComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- var beforeev = new StorageBeforeOpenEvent();
- RaiseLocalEvent(uid, ref beforeev);
- component.Open = true;
- EmptyContents(uid, component);
- ModifyComponents(uid, component);
- _audio.PlayPvs(component.OpenSound, uid);
- ReleaseGas(uid, component);
- var afterev = new StorageAfterOpenEvent();
- RaiseLocalEvent(uid, ref afterev);
- }
-
- public void CloseStorage(EntityUid uid, EntityStorageComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- component.Open = false;
-
- var targetCoordinates = new EntityCoordinates(uid, component.EnteringOffset);
-
- var entities = _lookup.GetEntitiesInRange(targetCoordinates, component.EnteringRange, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Sundries);
-
- var ev = new StorageBeforeCloseEvent(entities, new());
- RaiseLocalEvent(uid, ref ev);
- var count = 0;
- foreach (var entity in ev.Contents)
- {
- if (!ev.BypassChecks.Contains(entity))
- {
- if (!CanFit(entity, uid, component.Whitelist))
- continue;
- }
-
- if (!AddToContents(entity, uid, component))
- continue;
-
- count++;
- if (count >= component.Capacity)
- break;
- }
-
- TakeGas(uid, component);
- ModifyComponents(uid, component);
- _audio.PlayPvs(component.CloseSound, uid);
- component.LastInternalOpenAttempt = default;
- var afterev = new StorageAfterCloseEvent();
- RaiseLocalEvent(uid, ref afterev);
- }
-
- public bool Insert(EntityUid toInsert, EntityUid container, EntityStorageComponent? component = null)
- {
- if (!Resolve(container, ref component))
- return false;
-
- if (component.Open)
- {
- Transform(toInsert).WorldPosition = Transform(container).WorldPosition;
- return true;
- }
-
- var inside = EnsureComp(toInsert);
- inside.Storage = container;
- return component.Contents.Insert(toInsert, EntityManager);
- }
-
- public bool Remove(EntityUid toRemove, EntityUid container, EntityStorageComponent? component = null, TransformComponent? xform = null)
- {
- if (!Resolve(container, ref component, ref xform, false))
- return false;
-
- RemComp(toRemove);
- component.Contents.Remove(toRemove, EntityManager);
- Transform(toRemove).WorldPosition = xform.WorldPosition + xform.WorldRotation.RotateVec(component.EnteringOffset);
- return true;
- }
-
- public bool CanInsert(EntityUid container, EntityStorageComponent? component = null)
- {
- if (!Resolve(container, ref component))
- return false;
-
- if (component.Open)
- return true;
-
- if (component.Contents.ContainedEntities.Count >= component.Capacity)
- return false;
-
- return true;
- }
-
- public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false)
- {
- if (!CanOpen(user, target, silent))
- return false;
-
- OpenStorage(target);
- return true;
- }
-
- public bool TryCloseStorage(EntityUid target)
- {
- if (!CanClose(target))
- {
- return false;
- }
-
- CloseStorage(target);
- return true;
- }
-
- public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, EntityStorageComponent? component = null)
- {
- if (!Resolve(target, ref component))
- return false;
-
- if (!HasComp(user))
- return false;
-
- if (component.IsWeldedShut)
- {
- if (!silent && !component.Contents.Contains(user))
- _popupSystem.PopupEntity(Loc.GetString("entity-storage-component-welded-shut-message"), target);
-
- return false;
- }
-
- //Checks to see if the opening position, if offset, is inside of a wall.
- if (component.EnteringOffset != (0, 0) && !HasComp(target)) //if the entering position is offset
- {
- var newCoords = new EntityCoordinates(target, component.EnteringOffset);
- if (!_interactionSystem.InRangeUnobstructed(target, newCoords, 0, collisionMask: component.EnteringOffsetCollisionFlags))
- {
- if (!silent)
- _popupSystem.PopupEntity(Loc.GetString("entity-storage-component-cannot-open-no-space"), target);
- return false;
- }
- }
-
- var ev = new StorageOpenAttemptEvent(silent);
- RaiseLocalEvent(target, ref ev, true);
-
- return !ev.Cancelled;
- }
-
- public bool CanClose(EntityUid target, bool silent = false)
- {
- var ev = new StorageCloseAttemptEvent();
- RaiseLocalEvent(target, ref ev, silent);
-
- return !ev.Cancelled;
- }
-
- public bool AddToContents(EntityUid toAdd, EntityUid container, EntityStorageComponent? component = null)
- {
- if (!Resolve(container, ref component))
- return false;
-
- if (toAdd == container)
- return false;
-
- if (TryComp(toAdd, out var phys))
- {
- var aabb = _physics.GetWorldAABB(toAdd, body: phys);
-
- if (component.MaxSize < aabb.Size.X || component.MaxSize < aabb.Size.Y)
- return false;
- }
-
- return Insert(toAdd, container, component);
- }
-
- public bool CanFit(EntityUid toInsert, EntityUid container, EntityWhitelist? whitelist)
- {
- // conditions are complicated because of pizzabox-related issues, so follow this guide
- // 0. Accomplish your goals at all costs.
- // 1. AddToContents can block anything
- // 2. maximum item count can block anything
- // 3. ghosts can NEVER be eaten
- // 4. items can always be eaten unless a previous law prevents it
- // 5. if this is NOT AN ITEM, then mobs can always be eaten unless a previous
- // law prevents it
- // 6. if this is an item, then mobs must only be eaten if some other component prevents
- // pick-up interactions while a mob is inside (e.g. foldable)
- var attemptEvent = new InsertIntoEntityStorageAttemptEvent();
- RaiseLocalEvent(toInsert, ref attemptEvent);
- if (attemptEvent.Cancelled)
- return false;
-
- var targetIsMob = HasComp(toInsert);
- var storageIsItem = HasComp(container);
- var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp(toInsert);
-
- // BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
- // Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
- // Seriously, it is insanely hacky and weird to get someone out of a backpack once they end up in there.
- // And to be clear, they should NOT be in there.
- // For the record, what you need to do is empty the backpack onto a PlacableSurface (table, rack)
- if (targetIsMob)
- {
- if (!storageIsItem)
- allowedToEat = true;
- else
- {
- var storeEv = new StoreMobInItemContainerAttemptEvent();
- RaiseLocalEvent(container, ref storeEv);
- allowedToEat = storeEv.Handled && !storeEv.Cancelled;
- }
- }
-
- return allowedToEat;
- }
-
- public void ModifyComponents(EntityUid uid, EntityStorageComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (!component.IsCollidableWhenOpen && TryComp(uid, out var fixtures) && fixtures.Fixtures.Count > 0)
- {
- // currently only works for single-fixture entities. If they have more than one fixture, then
- // RemovedMasks needs to be tracked separately for each fixture, using a fixture Id Dictionary. Also the
- // fixture IDs probably cant be automatically generated without causing issues, unless there is some
- // guarantee that they will get deserialized with the same auto-generated ID when saving+loading the map.
- var fixture = fixtures.Fixtures.Values.First();
-
- if (component.Open)
- {
- component.RemovedMasks = fixture.CollisionLayer & component.MasksToRemove;
- _physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer & ~component.MasksToRemove, manager: fixtures);
- }
- else
- {
- _physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer | component.RemovedMasks, manager: fixtures);
- component.RemovedMasks = 0;
- }
- }
-
- if (TryComp(uid, out var surface))
- _placeableSurface.SetPlaceable(uid, component.Open, surface);
-
- _appearance.SetData(uid, StorageVisuals.Open, component.Open);
- _appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0);
- }
-
- private void TakeGas(EntityUid uid, EntityStorageComponent component)
+ protected override void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
{
if (!component.Airtight)
return;
- var tile = GetOffsetTileRef(uid, component);
+ var serverComp = (EntityStorageComponent) component;
+ var tile = GetOffsetTileRef(uid, serverComp);
if (tile != null && _atmos.GetTileMixture(tile.Value.GridUid, null, tile.Value.GridIndices, true) is {} environment)
{
- _atmos.Merge(component.Air, environment.RemoveVolume(EntityStorageComponent.GasMixVolume));
+ _atmos.Merge(serverComp.Air, environment.RemoveVolume(SharedEntityStorageComponent.GasMixVolume));
}
}
- public void ReleaseGas(EntityUid uid, EntityStorageComponent component)
+ public override void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
{
- if (!component.Airtight)
+ var serverComp = (EntityStorageComponent) component;
+
+ if (!serverComp.Airtight)
return;
- var tile = GetOffsetTileRef(uid, component);
+ var tile = GetOffsetTileRef(uid, serverComp);
if (tile != null && _atmos.GetTileMixture(tile.Value.GridUid, null, tile.Value.GridIndices, true) is {} environment)
{
- _atmos.Merge(environment, component.Air);
- component.Air.Clear();
+ _atmos.Merge(environment, serverComp.Air);
+ serverComp.Air.Clear();
}
}
diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs
index 42ebca5515..91510e339a 100644
--- a/Content.Server/Storage/EntitySystems/StorageSystem.cs
+++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs
@@ -77,9 +77,6 @@ namespace Content.Server.Storage.EntitySystems
SubscribeLocalEvent>(OnDoAfter);
- SubscribeLocalEvent>(AddToggleOpenVerb);
- SubscribeLocalEvent(OnRelayMovement);
-
SubscribeLocalEvent(OnStorageFillMapInit);
}
@@ -95,41 +92,6 @@ namespace Content.Server.Storage.EntitySystems
UpdateStorageUI(uid, storageComp);
}
- private void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
- {
- if (!EntityManager.HasComponent(args.Entity) || _gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
- return;
-
- component.LastInternalOpenAttempt = _gameTiming.CurTime;
- if (component.OpenOnMove)
- {
- _entityStorage.TryOpenStorage(args.Entity, component.Owner);
- }
- }
-
-
- private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract || !_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
- return;
-
- InteractionVerb verb = new();
- if (component.Open)
- {
- verb.Text = Loc.GetString("verb-common-close");
- verb.Icon = new SpriteSpecifier.Texture(
- new ResourcePath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
- }
- else
- {
- verb.Text = Loc.GetString("verb-common-open");
- verb.Icon = new SpriteSpecifier.Texture(
- new ResourcePath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
- }
- verb.Act = () => _entityStorage.ToggleOpen(args.User, args.Target, component);
- args.Verbs.Add(verb);
- }
-
private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetVerbsEvent args)
{
if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked)
diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs
index 4c27252db8..5c01ae0702 100644
--- a/Content.Shared/Lock/LockSystem.cs
+++ b/Content.Shared/Lock/LockSystem.cs
@@ -86,7 +86,7 @@ public sealed class LockSystem : EntitySystem
{
if (!component.Locked)
return;
- if (!args.Silent)
+ if (!args.Silent && _net.IsServer)
_sharedPopupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid);
args.Cancelled = true;
@@ -118,12 +118,12 @@ public sealed class LockSystem : EntitySystem
if (!HasUserAccess(uid, user, quiet: false))
return false;
- if (_net.IsClient && _timing.IsFirstTimePredicted)
+ if (_net.IsServer)
{
_sharedPopupSystem.PopupEntity(Loc.GetString("lock-comp-do-lock-success",
("entityName", Identity.Name(uid, EntityManager))), uid, user);
- _audio.PlayPvs(_audio.GetSound(lockComp.LockSound), uid, AudioParams.Default.WithVolume(-5));
}
+ _audio.PlayPredicted(lockComp.LockSound, uid, user, AudioParams.Default.WithVolume(-5));
lockComp.Locked = true;
_appearanceSystem.SetData(uid, StorageVisuals.Locked, true);
@@ -145,15 +145,15 @@ public sealed class LockSystem : EntitySystem
if (!Resolve(uid, ref lockComp))
return;
- if (_net.IsClient && _timing.IsFirstTimePredicted)
+ if (_net.IsServer)
{
if (user is { Valid: true })
{
_sharedPopupSystem.PopupEntity(Loc.GetString("lock-comp-do-unlock-success",
("entityName", Identity.Name(uid, EntityManager))), uid, user.Value);
}
- _audio.PlayPvs(_audio.GetSound(lockComp.UnlockSound), uid, AudioParams.Default.WithVolume(-5));
}
+ _audio.PlayPredicted(lockComp.UnlockSound, uid, user, AudioParams.Default.WithVolume(-5));
lockComp.Locked = false;
_appearanceSystem.SetData(uid, StorageVisuals.Locked, false);
@@ -236,10 +236,7 @@ public sealed class LockSystem : EntitySystem
{
if (!component.Locked)
return;
- if (_net.IsClient && _timing.IsFirstTimePredicted)
- {
- _audio.PlayPvs(_audio.GetSound(component.UnlockSound), uid, AudioParams.Default.WithVolume(-5));
- }
+ _audio.PlayPredicted(component.UnlockSound, uid, null, AudioParams.Default.WithVolume(-5));
_appearanceSystem.SetData(uid, StorageVisuals.Locked, false);
RemComp(uid); //Literally destroys the lock as a tell it was emagged
args.Handled = true;
diff --git a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs
index 8ea0940763..1f5c64d37f 100644
--- a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs
+++ b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs
@@ -25,7 +25,7 @@ namespace Content.Shared.Placeable
public void SetPlaceable(EntityUid uid, bool isPlaceable, PlaceableSurfaceComponent? surface = null)
{
- if (!Resolve(uid, ref surface))
+ if (!Resolve(uid, ref surface, false))
return;
surface.IsPlaceable = isPlaceable;
diff --git a/Content.Server/Storage/Components/InsideEntityStorageComponent.cs b/Content.Shared/Storage/Components/InsideEntityStorageComponent.cs
similarity index 82%
rename from Content.Server/Storage/Components/InsideEntityStorageComponent.cs
rename to Content.Shared/Storage/Components/InsideEntityStorageComponent.cs
index 4a4ef0f016..802cf195c3 100644
--- a/Content.Server/Storage/Components/InsideEntityStorageComponent.cs
+++ b/Content.Shared/Storage/Components/InsideEntityStorageComponent.cs
@@ -1,4 +1,4 @@
-namespace Content.Server.Storage.Components;
+namespace Content.Shared.Storage.Components;
///
/// Added to entities contained within entity storage, for directed event purposes.
diff --git a/Content.Shared/Storage/Components/SharedEntityStorage.cs b/Content.Shared/Storage/Components/SharedEntityStorage.cs
deleted file mode 100644
index a3399e2926..0000000000
--- a/Content.Shared/Storage/Components/SharedEntityStorage.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Content.Shared.Storage.Components;
-
-[ByRefEvent]
-public record struct InsertIntoEntityStorageAttemptEvent(bool Cancelled = false);
-
-[ByRefEvent]
-public record struct StoreMobInItemContainerAttemptEvent(bool Handled, bool Cancelled = false);
-
-[ByRefEvent]
-public record struct StorageOpenAttemptEvent(bool Silent, bool Cancelled = false);
-
-[ByRefEvent]
-public readonly record struct StorageBeforeOpenEvent;
-
-[ByRefEvent]
-public readonly record struct StorageAfterOpenEvent;
-
-[ByRefEvent]
-public record struct StorageCloseAttemptEvent(bool Cancelled = false);
-
-[ByRefEvent]
-public readonly record struct StorageBeforeCloseEvent(HashSet Contents, HashSet BypassChecks);
-
-[ByRefEvent]
-public readonly record struct StorageAfterCloseEvent;
diff --git a/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs b/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
new file mode 100644
index 0000000000..bbabde8e7d
--- /dev/null
+++ b/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
@@ -0,0 +1,178 @@
+using Content.Shared.Physics;
+using Content.Shared.Whitelist;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage.Components;
+
+[NetworkedComponent]
+public abstract class SharedEntityStorageComponent : Component
+{
+ public readonly float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage.
+ public const float GasMixVolume = 70f;
+
+ public static readonly TimeSpan InternalOpenAttemptDelay = TimeSpan.FromSeconds(0.5);
+ public TimeSpan LastInternalOpenAttempt;
+
+ ///
+ /// Collision masks that get removed when the storage gets opened.
+ ///
+ public readonly int MasksToRemove = (int) (
+ CollisionGroup.MidImpassable |
+ CollisionGroup.HighImpassable |
+ CollisionGroup.LowImpassable);
+
+ ///
+ /// Collision masks that were removed from ANY layer when the storage was opened;
+ ///
+ [DataField("removedMasks")]
+ public int RemovedMasks;
+
+ ///
+ /// The total amount of items that can fit in one entitystorage
+ ///
+ [DataField("capacity")]
+ public int Capacity = 30;
+
+ ///
+ /// Whether or not the entity still has collision when open
+ ///
+ [DataField("isCollidableWhenOpen")]
+ public bool IsCollidableWhenOpen;
+
+ ///
+ /// If true, it opens the storage when the entity inside of it moves
+ /// If false, it prevents the storage from opening when the entity inside of it moves.
+ /// This is for objects that you want the player to move while inside, like large cardboard boxes, without opening the storage.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("openOnMove")]
+ public bool OpenOnMove = true;
+
+ //The offset for where items are emptied/vacuumed for the EntityStorage.
+ [DataField("enteringOffset")]
+ public Vector2 EnteringOffset = new(0, 0);
+
+ //The collision groups checked, so that items are depositied or grabbed from inside walls.
+ [DataField("enteringOffsetCollisionFlags")]
+ public readonly CollisionGroup EnteringOffsetCollisionFlags = CollisionGroup.Impassable | CollisionGroup.MidImpassable;
+
+ ///
+ /// How close you have to be to the "entering" spot to be able to enter
+ ///
+ [DataField("enteringRange")]
+ public float EnteringRange = 0.18f;
+
+ ///
+ /// Whether or not to show the contents when the storage is closed
+ ///
+ [DataField("showContents")]
+ public bool ShowContents;
+
+ ///
+ /// Whether or not light is occluded by the storage
+ ///
+ [DataField("occludesLight")]
+ public bool OccludesLight = true;
+
+ ///
+ /// Whether or not all the contents stored should be deleted with the entitystorage
+ ///
+ [DataField("deleteContentsOnDestruction"), ViewVariables(VVAccess.ReadWrite)]
+ public bool DeleteContentsOnDestruction;
+
+ ///
+ /// Whether or not the container is sealed and traps air inside of it
+ ///
+ [DataField("airtight"), ViewVariables(VVAccess.ReadWrite)]
+ public bool Airtight = true;
+
+ ///
+ /// Whether or not the entitystorage is open or closed
+ ///
+ [DataField("open")]
+ public bool Open;
+
+ ///
+ /// The sound made when closed
+ ///
+ [DataField("closeSound")]
+ public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/closetclose.ogg");
+
+ ///
+ /// The sound made when open
+ ///
+ [DataField("openSound")]
+ public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg");
+
+ ///
+ /// Whitelist for what entities are allowed to be inserted into this container. If this is not null, the
+ /// standard requirement that the entity must be an item or mob is waived.
+ ///
+ [DataField("whitelist")]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// The contents of the storage
+ ///
+ [ViewVariables]
+ public Container Contents = default!;
+
+ ///
+ /// Whether or not the storage has been welded shut
+ ///
+ [DataField("isWeldedShut"), ViewVariables(VVAccess.ReadWrite)]
+ public bool IsWeldedShut;
+}
+
+[Serializable, NetSerializable]
+public sealed class EntityStorageComponentState : ComponentState
+{
+ public bool Open;
+
+ public int Capacity;
+
+ public bool IsCollidableWhenOpen;
+
+ public bool OpenOnMove;
+
+ public float EnteringRange;
+
+ public bool IsWeldedShut;
+
+ public EntityStorageComponentState(bool open, int capacity, bool isCollidableWhenOpen, bool openOnMove, float enteringRange, bool isWeldedShut)
+ {
+ Open = open;
+ Capacity = capacity;
+ IsCollidableWhenOpen = isCollidableWhenOpen;
+ OpenOnMove = openOnMove;
+ EnteringRange = enteringRange;
+ IsWeldedShut = isWeldedShut;
+ }
+}
+
+[ByRefEvent]
+public record struct InsertIntoEntityStorageAttemptEvent(bool Cancelled = false);
+
+[ByRefEvent]
+public record struct StoreMobInItemContainerAttemptEvent(bool Handled, bool Cancelled = false);
+
+[ByRefEvent]
+public record struct StorageOpenAttemptEvent(bool Silent, bool Cancelled = false);
+
+[ByRefEvent]
+public readonly record struct StorageBeforeOpenEvent;
+
+[ByRefEvent]
+public readonly record struct StorageAfterOpenEvent;
+
+[ByRefEvent]
+public record struct StorageCloseAttemptEvent(bool Cancelled = false);
+
+[ByRefEvent]
+public readonly record struct StorageBeforeCloseEvent(HashSet Contents, HashSet BypassChecks);
+
+[ByRefEvent]
+public readonly record struct StorageAfterCloseEvent;
diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
new file mode 100644
index 0000000000..cc2a627141
--- /dev/null
+++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
@@ -0,0 +1,455 @@
+using System.Linq;
+using Content.Shared.Body.Components;
+using Content.Shared.Destructible;
+using Content.Shared.Hands.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Item;
+using Content.Shared.Lock;
+using Content.Shared.Movement.Events;
+using Content.Shared.Placeable;
+using Content.Shared.Popups;
+using Content.Shared.Storage.Components;
+using Content.Shared.Verbs;
+using Content.Shared.Wall;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+public abstract class SharedEntityStorageSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] protected readonly SharedPopupSystem Popup = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public const string ContainerName = "entity_storage";
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnInteract, after: new[]{typeof(LockSystem)});
+ SubscribeLocalEvent(OnLockToggleAttempt);
+ SubscribeLocalEvent(OnDestruction);
+ SubscribeLocalEvent>(AddToggleOpenVerb);
+ SubscribeLocalEvent(OnRelayMovement);
+
+ SubscribeLocalEvent(OnGetState);
+ SubscribeLocalEvent(OnHandleState);
+ }
+
+ private void OnGetState(EntityUid uid, SharedEntityStorageComponent component, ref ComponentGetState args)
+ {
+ args.State = new EntityStorageComponentState(component.Open,
+ component.Capacity,
+ component.IsCollidableWhenOpen,
+ component.OpenOnMove,
+ component.EnteringRange,
+ component.IsWeldedShut);
+ }
+
+ private void OnHandleState(EntityUid uid, SharedEntityStorageComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not EntityStorageComponentState state)
+ return;
+ component.Open = state.Open;
+ component.Capacity = state.Capacity;
+ component.IsCollidableWhenOpen = state.IsCollidableWhenOpen;
+ component.OpenOnMove = state.OpenOnMove;
+ component.EnteringRange = state.EnteringRange;
+ component.IsWeldedShut = state.IsWeldedShut;
+ }
+
+ protected virtual void OnInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
+ {
+ component.Contents = _container.EnsureContainer(uid, ContainerName);
+ component.Contents.ShowContents = component.ShowContents;
+ component.Contents.OccludesLight = component.OccludesLight;
+ }
+
+ private void OnInteract(EntityUid uid, SharedEntityStorageComponent component, ActivateInWorldEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+ ToggleOpen(args.User, uid, component);
+ }
+
+ private void OnLockToggleAttempt(EntityUid uid, SharedEntityStorageComponent target, ref LockToggleAttemptEvent args)
+ {
+ // Cannot (un)lock open lockers.
+ if (target.Open)
+ args.Cancelled = true;
+
+ // Cannot (un)lock from the inside. Maybe a bad idea? Security jocks could trap nerds in lockers?
+ if (target.Contents.Contains(args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnDestruction(EntityUid uid, SharedEntityStorageComponent component, DestructionEventArgs args)
+ {
+ component.Open = true;
+ Dirty(component);
+ if (!component.DeleteContentsOnDestruction)
+ {
+ EmptyContents(uid, component);
+ return;
+ }
+
+ foreach (var ent in new List(component.Contents.ContainedEntities))
+ {
+ Del(ent);
+ }
+ }
+
+ private void OnRelayMovement(EntityUid uid, SharedEntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
+ {
+ if (!HasComp(args.Entity))
+ return;
+
+ if (_timing.CurTime < component.LastInternalOpenAttempt + SharedEntityStorageComponent.InternalOpenAttemptDelay)
+ return;
+
+ component.LastInternalOpenAttempt = _timing.CurTime;
+ if (component.OpenOnMove)
+ {
+ TryOpenStorage(args.Entity, uid);
+ }
+ }
+
+ private void AddToggleOpenVerb(EntityUid uid, SharedEntityStorageComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract)
+ return;
+
+ if (!CanOpen(args.User, args.Target, silent: true, component))
+ return;
+
+ InteractionVerb verb = new();
+ if (component.Open)
+ {
+ verb.Text = Loc.GetString("verb-common-close");
+ verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
+ }
+ else
+ {
+ verb.Text = Loc.GetString("verb-common-open");
+ verb.Icon = new SpriteSpecifier.Texture(
+ new ResourcePath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
+ }
+ verb.Act = () => ToggleOpen(args.User, args.Target, component);
+ args.Verbs.Add(verb);
+ }
+
+
+ public void ToggleOpen(EntityUid user, EntityUid target, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(target, ref component))
+ return;
+
+ if (component.Open)
+ {
+ TryCloseStorage(target);
+ }
+ else
+ {
+ TryOpenStorage(user, target);
+ }
+ }
+
+ public void EmptyContents(EntityUid uid, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ var uidXform = Transform(uid);
+ var containedArr = component.Contents.ContainedEntities.ToArray();
+ foreach (var contained in containedArr)
+ {
+ Remove(contained, uid, component, uidXform);
+ }
+ }
+
+ public void OpenStorage(EntityUid uid, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ var beforeev = new StorageBeforeOpenEvent();
+ RaiseLocalEvent(uid, ref beforeev);
+ component.Open = true;
+ Dirty(component);
+ EmptyContents(uid, component);
+ ModifyComponents(uid, component);
+ if (_net.IsClient && _timing.IsFirstTimePredicted)
+ _audio.PlayPvs(component.OpenSound, uid);
+ ReleaseGas(uid, component);
+ var afterev = new StorageAfterOpenEvent();
+ RaiseLocalEvent(uid, ref afterev);
+ }
+
+ public void CloseStorage(EntityUid uid, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+ component.Open = false;
+ Dirty(component);
+
+ var targetCoordinates = new EntityCoordinates(uid, component.EnteringOffset);
+
+ var entities = _lookup.GetEntitiesInRange(targetCoordinates, component.EnteringRange, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Sundries);
+
+ var ev = new StorageBeforeCloseEvent(entities, new());
+ RaiseLocalEvent(uid, ref ev);
+ var count = 0;
+ foreach (var entity in ev.Contents)
+ {
+ if (!ev.BypassChecks.Contains(entity))
+ {
+ if (!CanFit(entity, uid, component.Whitelist))
+ continue;
+ }
+
+ if (!AddToContents(entity, uid, component))
+ continue;
+
+ count++;
+ if (count >= component.Capacity)
+ break;
+ }
+
+ TakeGas(uid, component);
+ ModifyComponents(uid, component);
+ if (_net.IsClient && _timing.IsFirstTimePredicted)
+ _audio.PlayPvs(component.CloseSound, uid);
+ component.LastInternalOpenAttempt = default;
+ var afterev = new StorageAfterCloseEvent();
+ RaiseLocalEvent(uid, ref afterev);
+ }
+
+ public bool Insert(EntityUid toInsert, EntityUid container, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(container, ref component))
+ return false;
+
+ if (component.Open)
+ {
+ _transform.SetWorldPosition(toInsert, _transform.GetWorldPosition(container));
+ return true;
+ }
+
+ var inside = EnsureComp(toInsert);
+ inside.Storage = container;
+ return component.Contents.Insert(toInsert, EntityManager);
+ }
+
+ public bool Remove(EntityUid toRemove, EntityUid container, SharedEntityStorageComponent? component = null, TransformComponent? xform = null)
+ {
+ if (!Resolve(container, ref component, ref xform, false))
+ return false;
+
+ RemComp(toRemove);
+ component.Contents.Remove(toRemove, EntityManager);
+ var pos = _transform.GetWorldPosition(xform) + _transform.GetWorldRotation(xform).RotateVec(component.EnteringOffset);
+ _transform.SetWorldPosition(toRemove, pos);
+ return true;
+ }
+
+ public bool CanInsert(EntityUid container, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(container, ref component))
+ return false;
+
+ if (component.Open)
+ return true;
+
+ if (component.Contents.ContainedEntities.Count >= component.Capacity)
+ return false;
+
+ return true;
+ }
+
+ public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false)
+ {
+ if (!CanOpen(user, target, silent))
+ return false;
+
+ OpenStorage(target);
+ return true;
+ }
+
+ public bool TryCloseStorage(EntityUid target)
+ {
+ if (!CanClose(target))
+ {
+ return false;
+ }
+
+ CloseStorage(target);
+ return true;
+ }
+
+ public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(target, ref component))
+ return false;
+
+ if (!HasComp(user))
+ return false;
+
+ if (component.IsWeldedShut)
+ {
+ if (!silent && !component.Contents.Contains(user) && _net.IsServer)
+ Popup.PopupEntity(Loc.GetString("entity-storage-component-welded-shut-message"), target);
+
+ return false;
+ }
+
+ //Checks to see if the opening position, if offset, is inside of a wall.
+ if (component.EnteringOffset != (0, 0) && !HasComp(target)) //if the entering position is offset
+ {
+ var newCoords = new EntityCoordinates(target, component.EnteringOffset);
+ if (!_interaction.InRangeUnobstructed(target, newCoords, 0, collisionMask: component.EnteringOffsetCollisionFlags))
+ {
+ if (!silent && _net.IsServer)
+ Popup.PopupEntity(Loc.GetString("entity-storage-component-cannot-open-no-space"), target);
+ return false;
+ }
+ }
+
+ var ev = new StorageOpenAttemptEvent(silent);
+ RaiseLocalEvent(target, ref ev, true);
+
+ return !ev.Cancelled;
+ }
+
+ public bool CanClose(EntityUid target, bool silent = false)
+ {
+ var ev = new StorageCloseAttemptEvent();
+ RaiseLocalEvent(target, ref ev, silent);
+
+ return !ev.Cancelled;
+ }
+
+ public bool AddToContents(EntityUid toAdd, EntityUid container, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(container, ref component))
+ return false;
+
+ if (toAdd == container)
+ return false;
+
+ if (TryComp(toAdd, out var phys))
+ {
+ var aabb = _physics.GetWorldAABB(toAdd, body: phys);
+
+ if (component.MaxSize < aabb.Size.X || component.MaxSize < aabb.Size.Y)
+ return false;
+ }
+
+ return Insert(toAdd, container, component);
+ }
+
+ public bool CanFit(EntityUid toInsert, EntityUid container, EntityWhitelist? whitelist)
+ {
+ // conditions are complicated because of pizzabox-related issues, so follow this guide
+ // 0. Accomplish your goals at all costs.
+ // 1. AddToContents can block anything
+ // 2. maximum item count can block anything
+ // 3. ghosts can NEVER be eaten
+ // 4. items can always be eaten unless a previous law prevents it
+ // 5. if this is NOT AN ITEM, then mobs can always be eaten unless a previous
+ // law prevents it
+ // 6. if this is an item, then mobs must only be eaten if some other component prevents
+ // pick-up interactions while a mob is inside (e.g. foldable)
+ var attemptEvent = new InsertIntoEntityStorageAttemptEvent();
+ RaiseLocalEvent(toInsert, ref attemptEvent);
+ if (attemptEvent.Cancelled)
+ return false;
+
+ var targetIsMob = HasComp(toInsert);
+ var storageIsItem = HasComp(container);
+ var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp(toInsert);
+
+ // BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
+ // Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
+ // Seriously, it is insanely hacky and weird to get someone out of a backpack once they end up in there.
+ // And to be clear, they should NOT be in there.
+ // For the record, what you need to do is empty the backpack onto a PlacableSurface (table, rack)
+ if (targetIsMob)
+ {
+ if (!storageIsItem)
+ allowedToEat = true;
+ else
+ {
+ var storeEv = new StoreMobInItemContainerAttemptEvent();
+ RaiseLocalEvent(container, ref storeEv);
+ allowedToEat = storeEv is { Handled: true, Cancelled: false };
+ }
+ }
+
+ return allowedToEat;
+ }
+
+ private void ModifyComponents(EntityUid uid, SharedEntityStorageComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (!component.IsCollidableWhenOpen && TryComp(uid, out var fixtures) &&
+ fixtures.Fixtures.Count > 0)
+ {
+ // currently only works for single-fixture entities. If they have more than one fixture, then
+ // RemovedMasks needs to be tracked separately for each fixture, using a fixture Id Dictionary. Also the
+ // fixture IDs probably cant be automatically generated without causing issues, unless there is some
+ // guarantee that they will get deserialized with the same auto-generated ID when saving+loading the map.
+ var fixture = fixtures.Fixtures.Values.First();
+
+ if (component.Open)
+ {
+ component.RemovedMasks = fixture.CollisionLayer & component.MasksToRemove;
+ _physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer & ~component.MasksToRemove,
+ manager: fixtures);
+ }
+ else
+ {
+ _physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer | component.RemovedMasks,
+ manager: fixtures);
+ component.RemovedMasks = 0;
+ }
+ }
+
+ if (TryComp(uid, out var surface))
+ _placeableSurface.SetPlaceable(uid, component.Open, surface);
+
+ _appearance.SetData(uid, StorageVisuals.Open, component.Open);
+ _appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0);
+ }
+
+ protected virtual void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
+ {
+
+ }
+
+ public virtual void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
+ {
+
+ }
+}