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