diff --git a/Content.Shared/Storage/Components/BinComponent.cs b/Content.Shared/Storage/Components/BinComponent.cs
new file mode 100644
index 0000000000..0dfb379275
--- /dev/null
+++ b/Content.Shared/Storage/Components/BinComponent.cs
@@ -0,0 +1,71 @@
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Storage.Components;
+
+///
+/// This is used for things like paper bins, in which
+/// you can only take off of the top of the bin.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(BinSystem))]
+public sealed class BinComponent : Component
+{
+ ///
+ /// The containers that contain the items held in the bin
+ ///
+ [ViewVariables]
+ public Container ItemContainer = default!;
+
+ ///
+ /// A list representing the order in which
+ /// all the entities are stored in the bin.
+ ///
+ ///
+ /// The only reason this isn't a stack is so that
+ /// i can handle entities being deleted and removed
+ /// out of order by other systems
+ ///
+ [DataField("items")]
+ public List Items = new();
+
+ ///
+ /// The items that start in the bin. Sorted in order.
+ ///
+ [DataField("initialContents", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List InitialContents = new();
+
+ ///
+ /// A whitelist governing what items can be inserted into the bin.
+ ///
+ [DataField("whitelist")]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// The maximum amount of items
+ /// that can be stored in the bin.
+ ///
+ [DataField("maxItems")]
+ public int MaxItems = 20;
+}
+
+[Serializable, NetSerializable]
+public sealed class BinComponentState : ComponentState
+{
+ public List Items;
+
+ public EntityWhitelist? Whitelist;
+
+ public int MaxItems;
+
+ public BinComponentState(List items, EntityWhitelist? whitelist, int maxItems)
+ {
+ Items = items;
+ Whitelist = whitelist;
+ MaxItems = maxItems;
+ }
+}
diff --git a/Content.Shared/Storage/EntitySystems/BinSystem.cs b/Content.Shared/Storage/EntitySystems/BinSystem.cs
new file mode 100644
index 0000000000..e5fe510671
--- /dev/null
+++ b/Content.Shared/Storage/EntitySystems/BinSystem.cs
@@ -0,0 +1,159 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Storage.Components;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+///
+/// This handles
+///
+public sealed class BinSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly ISharedAdminLogManager _admin = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+
+ public const string BinContainerId = "bin-container";
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnGetState);
+ SubscribeLocalEvent(OnHandleState);
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnEntRemoved);
+ SubscribeLocalEvent(OnInteractHand);
+ SubscribeLocalEvent(OnAfterInteractUsing);
+ }
+
+ private void OnGetState(EntityUid uid, BinComponent component, ref ComponentGetState args)
+ {
+ args.State = new BinComponentState(component.Items, component.Whitelist, component.MaxItems);
+ }
+
+ private void OnHandleState(EntityUid uid, BinComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not BinComponentState state)
+ return;
+
+ component.Items = new List(state.Items);
+ component.Whitelist = state.Whitelist;
+ component.MaxItems = state.MaxItems;
+ }
+
+ private void OnStartup(EntityUid uid, BinComponent component, ComponentStartup args)
+ {
+ component.ItemContainer = _container.EnsureContainer(uid, BinContainerId);
+ }
+
+ private void OnMapInit(EntityUid uid, BinComponent component, MapInitEvent args)
+ {
+ // don't spawn on the client.
+ if (_net.IsClient)
+ return;
+
+ var xform = Transform(uid);
+ foreach (var id in component.InitialContents)
+ {
+ var ent = Spawn(id, xform.Coordinates);
+ if (!TryInsertIntoBin(uid, ent, component))
+ {
+ Logger.Error($"Entity {ToPrettyString(ent)} was unable to be initialized into bin {ToPrettyString(uid)}");
+ return;
+ }
+ }
+ }
+
+ private void OnEntRemoved(EntityUid uid, BinComponent component, EntRemovedFromContainerMessage args)
+ {
+ component.Items.Remove(args.Entity);
+ }
+
+ private void OnInteractHand(EntityUid uid, BinComponent component, InteractHandEvent args)
+ {
+ if (args.Handled || !_timing.IsFirstTimePredicted)
+ return;
+
+ EntityUid? toGrab = component.Items.LastOrDefault();
+ if (!TryRemoveFromBin(uid, toGrab, component))
+ return;
+
+ _hands.TryPickupAnyHand(args.User, toGrab.Value);
+ _admin.Add(LogType.Pickup, LogImpact.Low,
+ $"{ToPrettyString(uid):player} removed {ToPrettyString(toGrab.Value)} from bin {ToPrettyString(uid)}.");
+ args.Handled = true;
+ }
+
+ private void OnAfterInteractUsing(EntityUid uid, BinComponent component, AfterInteractUsingEvent args)
+ {
+ if (args.Handled || !args.CanReach)
+ return;
+
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ if (!TryInsertIntoBin(uid, args.Used, component))
+ return;
+
+ _admin.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):player} inserted {ToPrettyString(args.User)} into bin {ToPrettyString(uid)}.");
+ args.Handled = true;
+ }
+
+ ///
+ /// Inserts an entity at the top of the bin
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryInsertIntoBin(EntityUid uid, EntityUid toInsert, BinComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (component.Items.Count >= component.MaxItems)
+ return false;
+
+ if (component.Whitelist != null && !component.Whitelist.IsValid(toInsert))
+ return false;
+
+ component.ItemContainer.Insert(toInsert);
+ component.Items.Add(toInsert);
+ Dirty(component);
+ return true;
+ }
+
+ ///
+ /// Tries to remove an entity from the top of the bin.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryRemoveFromBin(EntityUid uid, [NotNullWhen(true)] EntityUid? toRemove, BinComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (toRemove != component.Items.Last())
+ return false;
+
+ if (!component.ItemContainer.Remove(toRemove.Value))
+ return false;
+
+ component.Items.Remove(toRemove.Value);
+ Dirty(component);
+ return true;
+ }
+}
diff --git a/Resources/Prototypes/Entities/Structures/Storage/paper_bin.yml b/Resources/Prototypes/Entities/Structures/Storage/paper_bin.yml
new file mode 100644
index 0000000000..0482d1dd5d
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Storage/paper_bin.yml
@@ -0,0 +1,78 @@
+- type: entity
+ parent: BaseStructureDynamic
+ id: PaperBin
+ name: paper bin
+ description: What secrets lie at the bottom of its endless stack?
+ suffix: Empty
+ components:
+ - type: Sprite
+ netsync: false
+ sprite: Objects/Misc/bureaucracy.rsi
+ state: paper_bin0
+ drawdepth: SmallObjects
+ noRot: true
+ - type: Appearance
+ - type: ItemMapper
+ sprite: Objects/Misc/bureaucracy.rsi
+ mapLayers:
+ paper_bin1:
+ whitelist:
+ tags:
+ - Document
+ - Write
+ - type: Fixtures
+ fixtures:
+ - shape:
+ !type:PhysShapeAabb
+ bounds: "-0.10,-0.10,0.10,0.10"
+ density: 500
+ mask:
+ - TabletopMachineMask
+ - type: InteractionOutline
+ - type: Bin
+ whitelist:
+ tags:
+ - Document
+ - Write
+ - type: ContainerContainer
+ containers:
+ bin-container: !type:Container
+
+- type: entity
+ parent: PaperBin
+ id: PaperBin5
+ suffix: 5
+ components:
+ - type: Bin
+ initialContents:
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ whitelist:
+ tags:
+ - Document
+ - Write
+
+- type: entity
+ parent: PaperBin
+ id: PaperBin10
+ suffix: 10
+ components:
+ - type: Bin
+ initialContents:
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ - Paper
+ whitelist:
+ tags:
+ - Document
+ - Write
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
index ed8e17060a..d50251f6e3 100644
--- a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
+++ b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
@@ -96,12 +96,6 @@
{
"name": "paper_bin1"
},
- {
- "name": "paper_bin2"
- },
- {
- "name": "paper_bin3"
- },
{
"name": "paper_plane"
},
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin0.png b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin0.png
index d69da9123f..35ffa4cf85 100644
Binary files a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin0.png and b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin0.png differ
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin1.png b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin1.png
index 4c906f6238..cbff65725b 100644
Binary files a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin1.png and b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin1.png differ
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin2.png b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin2.png
deleted file mode 100644
index 26d41d6e8e..0000000000
Binary files a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin2.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin3.png b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin3.png
deleted file mode 100644
index 56cc52fffa..0000000000
Binary files a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_bin3.png and /dev/null differ