]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Predict entitystorage (#14082)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Sun, 26 Feb 2023 12:44:30 +0000 (07:44 -0500)
committerGitHub <noreply@github.com>
Sun, 26 Feb 2023 12:44:30 +0000 (23:44 +1100)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Content.Client/Storage/Components/EntityStorageComponent.cs [new file with mode: 0644]
Content.Client/Storage/Systems/EntityStorageSystem.cs [new file with mode: 0644]
Content.Server/Storage/Components/EntityStorageComponent.cs
Content.Server/Storage/EntitySystems/EntityStorageSystem.cs
Content.Server/Storage/EntitySystems/StorageSystem.cs
Content.Shared/Lock/LockSystem.cs
Content.Shared/Placeable/PlaceableSurfaceSystem.cs
Content.Shared/Storage/Components/InsideEntityStorageComponent.cs [moved from Content.Server/Storage/Components/InsideEntityStorageComponent.cs with 82% similarity]
Content.Shared/Storage/Components/SharedEntityStorage.cs [deleted file]
Content.Shared/Storage/Components/SharedEntityStorageComponent.cs [new file with mode: 0644]
Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs [new file with mode: 0644]

diff --git a/Content.Client/Storage/Components/EntityStorageComponent.cs b/Content.Client/Storage/Components/EntityStorageComponent.cs
new file mode 100644 (file)
index 0000000..65b510c
--- /dev/null
@@ -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 (file)
index 0000000..36532b0
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Storage.EntitySystems;
+
+namespace Content.Client.Storage.Systems;
+
+public sealed class EntityStorageSystem : SharedEntityStorageSystem
+{
+
+}
index 705ad00ab9dde16d981f27c49f7b479abff6fbb5..568c67bc06f9342a8718c2c829822b98d553c969 100644 (file)
@@ -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;
-
-    /// <summary>
-    ///     Collision masks that get removed when the storage gets opened.
-    /// </summary>
-    public readonly int MasksToRemove = (int) (
-        CollisionGroup.MidImpassable |
-        CollisionGroup.HighImpassable |
-        CollisionGroup.LowImpassable);
-
-    /// <summary>
-    ///     Collision masks that were removed from ANY layer when the storage was opened;
-    /// </summary>
-    [DataField("removedMasks")]
-    public int RemovedMasks;
-
-    [DataField("capacity")]
-    public int Capacity = 30;
-
-    [DataField("isCollidableWhenOpen")]
-    public bool IsCollidableWhenOpen;
-
-    /// <summary>
-    /// 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.
-    /// </summary>
-    [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;
-
-    /// <summary>
-    /// Whether or not the container is sealed and traps air inside of it
-    /// </summary>
-    [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");
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    [DataField("whitelist")]
-    public EntityWhitelist? Whitelist;
-
-    [ViewVariables]
-    public Container Contents = default!;
-
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool IsWeldedShut;
-
     /// <summary>
     ///     Gas currently contained in this entity storage.
     ///     None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.
index 3d2bb019cc6f36984769ac0d9685b5e79f80ae76..ebefd304f7947a9b6ecc36d5b8291a2dc464aa25 100644 (file)
@@ -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<EntityStorageComponent, ComponentInit>(OnInit);
-        SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract);
         SubscribeLocalEvent<EntityStorageComponent, WeldableAttemptEvent>(OnWeldableAttempt);
         SubscribeLocalEvent<EntityStorageComponent, WeldableChangedEvent>(OnWelded);
-        SubscribeLocalEvent<EntityStorageComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
-        SubscribeLocalEvent<EntityStorageComponent, DestructionEventArgs>(OnDestruction);
 
-        SubscribeLocalEvent<InsideEntityStorageComponent, EntGotRemovedFromContainerMessage>(OnRemoved);
         SubscribeLocalEvent<InsideEntityStorageComponent, InhaleLocationEvent>(OnInsideInhale);
         SubscribeLocalEvent<InsideEntityStorageComponent, ExhaleLocationEvent>(OnInsideExhale);
         SubscribeLocalEvent<InsideEntityStorageComponent, AtmosExposedGetAirEvent>(OnInsideExposed);
 
+        SubscribeLocalEvent<InsideEntityStorageComponent, EntGotRemovedFromContainerMessage>(OnRemoved);
     }
 
-    private void OnInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
+    protected override void OnInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
     {
-        component.Contents = _container.EnsureContainer<Container>(uid, ContainerName);
-        component.Contents.ShowContents = component.ShowContents;
-        component.Contents.OccludesLight = component.OccludesLight;
+        base.OnInit(uid, component, args);
 
         if (TryComp<ConstructionComponent>(uid, out var construction))
             _construction.AddContainer(uid, ContainerName, construction);
 
-        if (TryComp<PlaceableSurfaceComponent>(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<EntityUid>(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<InsideEntityStorageComponent>(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<InsideEntityStorageComponent>(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<SharedHandsComponent>(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<WallMountComponent>(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<PhysicsComponent>(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<BodyComponent>(toInsert);
-        var storageIsItem = HasComp<ItemComponent>(container);
-        var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp<ItemComponent>(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<FixturesComponent>(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<PlaceableSurfaceComponent>(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();
         }
     }
 
index 42ebca5515a83e5a9025235f521d001ef17ed384..91510e339a3f8fc51ad6f3a4c4243860e4998256 100644 (file)
@@ -77,9 +77,6 @@ namespace Content.Server.Storage.EntitySystems
 
             SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
 
-            SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
-            SubscribeLocalEvent<EntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
-
             SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(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<HandsComponent>(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<InteractionVerb> 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<ActivationVerb> args)
         {
             if (!args.CanAccess || !args.CanInteract || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
index 4c27252db813d9cad0ca3a75687217aea775c8a6..5c01ae0702d8053eb3ce6f144cd0fdabfb92b5cb 100644 (file)
@@ -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<LockComponent>(uid); //Literally destroys the lock as a tell it was emagged
         args.Handled = true;
index 8ea09407634cdc3fb4384827391bf83b045a27c0..1f5c64d37f803d17742aae480b495f36de579d2d 100644 (file)
@@ -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;
similarity index 82%
rename from Content.Server/Storage/Components/InsideEntityStorageComponent.cs
rename to Content.Shared/Storage/Components/InsideEntityStorageComponent.cs
index 4a4ef0f01654d86040a0037982a8d35340d5124e..802cf195c34095f02eec7dc69fa70f1d7485c774 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.Storage.Components;
+namespace Content.Shared.Storage.Components;
 
 /// <summary>
 ///     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 (file)
index a3399e2..0000000
+++ /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<EntityUid> Contents, HashSet<EntityUid> 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 (file)
index 0000000..bbabde8
--- /dev/null
@@ -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;
+
+    /// <summary>
+    ///     Collision masks that get removed when the storage gets opened.
+    /// </summary>
+    public readonly int MasksToRemove = (int) (
+        CollisionGroup.MidImpassable |
+        CollisionGroup.HighImpassable |
+        CollisionGroup.LowImpassable);
+
+    /// <summary>
+    ///     Collision masks that were removed from ANY layer when the storage was opened;
+    /// </summary>
+    [DataField("removedMasks")]
+    public int RemovedMasks;
+
+    /// <summary>
+    /// The total amount of items that can fit in one entitystorage
+    /// </summary>
+    [DataField("capacity")]
+    public int Capacity = 30;
+
+    /// <summary>
+    /// Whether or not the entity still has collision when open
+    /// </summary>
+    [DataField("isCollidableWhenOpen")]
+    public bool IsCollidableWhenOpen;
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [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;
+
+    /// <summary>
+    /// How close you have to be to the "entering" spot to be able to enter
+    /// </summary>
+    [DataField("enteringRange")]
+    public float EnteringRange = 0.18f;
+
+    /// <summary>
+    /// Whether or not to show the contents when the storage is closed
+    /// </summary>
+    [DataField("showContents")]
+    public bool ShowContents;
+
+    /// <summary>
+    /// Whether or not light is occluded by the storage
+    /// </summary>
+    [DataField("occludesLight")]
+    public bool OccludesLight = true;
+
+    /// <summary>
+    /// Whether or not all the contents stored should be deleted with the entitystorage
+    /// </summary>
+    [DataField("deleteContentsOnDestruction"), ViewVariables(VVAccess.ReadWrite)]
+    public bool DeleteContentsOnDestruction;
+
+    /// <summary>
+    /// Whether or not the container is sealed and traps air inside of it
+    /// </summary>
+    [DataField("airtight"), ViewVariables(VVAccess.ReadWrite)]
+    public bool Airtight = true;
+
+    /// <summary>
+    /// Whether or not the entitystorage is open or closed
+    /// </summary>
+    [DataField("open")]
+    public bool Open;
+
+    /// <summary>
+    /// The sound made when closed
+    /// </summary>
+    [DataField("closeSound")]
+    public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/closetclose.ogg");
+
+    /// <summary>
+    /// The sound made when open
+    /// </summary>
+    [DataField("openSound")]
+    public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg");
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    [DataField("whitelist")]
+    public EntityWhitelist? Whitelist;
+
+    /// <summary>
+    /// The contents of the storage
+    /// </summary>
+    [ViewVariables]
+    public Container Contents = default!;
+
+    /// <summary>
+    /// Whether or not the storage has been welded shut
+    /// </summary>
+    [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<EntityUid> Contents, HashSet<EntityUid> 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 (file)
index 0000000..cc2a627
--- /dev/null
@@ -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";
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<SharedEntityStorageComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<SharedEntityStorageComponent, ActivateInWorldEvent>(OnInteract, after: new[]{typeof(LockSystem)});
+        SubscribeLocalEvent<SharedEntityStorageComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
+        SubscribeLocalEvent<SharedEntityStorageComponent, DestructionEventArgs>(OnDestruction);
+        SubscribeLocalEvent<SharedEntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
+        SubscribeLocalEvent<SharedEntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
+
+        SubscribeLocalEvent<SharedEntityStorageComponent, ComponentGetState>(OnGetState);
+        SubscribeLocalEvent<SharedEntityStorageComponent, ComponentHandleState>(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<Container>(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<EntityUid>(component.Contents.ContainedEntities))
+        {
+            Del(ent);
+        }
+    }
+
+    private void OnRelayMovement(EntityUid uid, SharedEntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
+    {
+        if (!HasComp<SharedHandsComponent>(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<InteractionVerb> 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<InsideEntityStorageComponent>(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<InsideEntityStorageComponent>(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<SharedHandsComponent>(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<WallMountComponent>(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<PhysicsComponent>(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<BodyComponent>(toInsert);
+        var storageIsItem = HasComp<ItemComponent>(container);
+        var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp<ItemComponent>(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<FixturesComponent>(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<PlaceableSurfaceComponent>(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)
+    {
+
+    }
+}