using Content.Client.Items.Components;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
+using Content.Shared.IdentityManagement;
using Content.Shared.Item;
+using Content.Shared.Stacks;
using Robust.Client.UserInterface;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using static Content.Shared.Storage.SharedStorageComponent;
public StorageWindow(IEntityManager entityManager)
{
_entityManager = entityManager;
- SetSize = (200, 320);
+ SetSize = (240, 320);
Title = Loc.GetString("comp-storage-window-title");
RectClipContent = true;
_entityManager.TryGetComponent(entity, out SpriteComponent? sprite);
_entityManager.TryGetComponent(entity, out ItemComponent? item);
+ _entityManager.TryGetComponent(entity, out StackComponent? stack);
+ var count = stack?.Count ?? 1;
+ var size = item?.Size;
button.AddChild(new BoxContainer
{
{
HorizontalExpand = true,
ClipText = true,
- Text = _entityManager.GetComponent<MetaDataComponent>(entity).EntityName
+ Text = _entityManager.GetComponent<MetaDataComponent>(Identity.Entity(entity, _entityManager)).EntityName +
+ (count > 1 ? $" x {count}" : string.Empty),
},
new Label
{
Align = Label.AlignMode.Right,
- Text = item?.Size.ToString() ?? Loc.GetString("comp-storage-no-item-size"),
+ Text = size.ToString() ?? Loc.GetString("comp-storage-no-item-size"),
}
}
});
+using Content.Server.Storage.Components;
+using Content.Server.Storage.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
using JetBrains.Annotations;
+using Robust.Server.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
[UsedImplicitly]
public sealed class StackSystem : SharedStackSystem
{
+ [Dependency] private readonly ContainerSystem _container = default!;
+ [Dependency] private readonly StorageSystem _storage = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split)
return;
+ if (_container.TryGetContainingContainer(uid, out var container) &&
+ TryComp<ServerStorageComponent>(container.Owner, out var storage))
+ {
+ _storage.UpdateStorageUI(container.Owner, storage);
+ }
+
HandsSystem.PickupOrDrop(userUid, split);
PopupSystem.PopupCursor(Loc.GetString("comp-stack-split"), userUid);
using Content.Server.Ghost.Components;
using Content.Server.Interaction;
using Content.Server.Popups;
+using Content.Server.Stack;
using Content.Server.Storage.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
namespace Content.Server.Storage.EntitySystems
{
- [UsedImplicitly]
public sealed partial class StorageSystem : EntitySystem
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
/// <inheritdoc />
_appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen);
}
- private void RecalculateStorageUsed(ServerStorageComponent storageComp)
+ public void RecalculateStorageUsed(ServerStorageComponent storageComp)
{
storageComp.StorageUsed = 0;
storageComp.SizeCache.Clear();
if (!itemQuery.TryGetComponent(entity, out var itemComp))
continue;
- storageComp.StorageUsed += itemComp.Size;
- storageComp.SizeCache.Add(entity, itemComp.Size);
+ var size = itemComp.Size;
+ storageComp.StorageUsed += size;
+ storageComp.SizeCache.Add(entity, size);
}
}
+ public int GetAvailableSpace(EntityUid uid, ServerStorageComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return 0;
+
+ return component.StorageCapacityMax - component.StorageUsed;
+ }
+
/// <summary>
/// Move entities from one storage to another.
/// </summary>
return false;
}
- if (TryComp(insertEnt, out ItemComponent? itemComp) &&
+ if (!HasComp<StackComponent>(insertEnt) && TryComp(insertEnt, out ItemComponent? itemComp) &&
itemComp.Size > storageComp.StorageCapacityMax - storageComp.StorageUsed)
{
reason = "comp-storage-insufficient-capacity";
/// <returns>true if the entity was inserted, false otherwise</returns>
public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null, bool playSound = true)
{
- if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
+ if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage == null)
return false;
+ /*
+ * 1. If the inserted thing is stackable then try to stack it to existing stacks
+ * 2. If anything remains insert whatever is possible.
+ * 3. If insertion is not possible then leave the stack as is.
+ * At either rate still play the insertion sound
+ *
+ * For now we just treat items as always being the same size regardless of stack count.
+ */
+
+ // If it's stackable then prefer to stack it
+ var stackQuery = GetEntityQuery<StackComponent>();
+
+ if (stackQuery.TryGetComponent(insertEnt, out var insertStack))
+ {
+ var toInsertCount = insertStack.Count;
+
+ foreach (var ent in storageComp.Storage.ContainedEntities)
+ {
+ if (!stackQuery.TryGetComponent(ent, out var containedStack) || !insertStack.StackTypeId.Equals(containedStack.StackTypeId))
+ continue;
+
+ if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack))
+ continue;
+
+ var remaining = insertStack.Count;
+ toInsertCount -= toInsertCount - remaining;
+
+ if (remaining > 0)
+ continue;
+
+ break;
+ }
+
+ // Still stackable remaining
+ if (insertStack.Count > 0)
+ {
+ // Try to insert it as a new stack.
+ if (TryComp(insertEnt, out ItemComponent? itemComp) &&
+ itemComp.Size > storageComp.StorageCapacityMax - storageComp.StorageUsed ||
+ !storageComp.Storage.Insert(insertEnt))
+ {
+ // If we also didn't do any stack fills above then just end
+ // otherwise play sound and update UI anyway.
+ if (toInsertCount == insertStack.Count)
+ return false;
+ }
+ }
+ }
+ // Non-stackable but no insertion for reasons.
+ else if (!storageComp.Storage.Insert(insertEnt))
+ {
+ return false;
+ }
+
if (playSound && storageComp.StorageInsertSound is not null)
_audio.PlayPvs(storageComp.StorageInsertSound, uid);
}
}
- private void UpdateStorageUI(EntityUid uid, ServerStorageComponent storageComp)
+ public void UpdateStorageUI(EntityUid uid, ServerStorageComponent storageComp)
{
if (storageComp.Storage == null)
return;
if (!Resolve(recipient, ref recipientStack, false) || !Resolve(donor, ref donorStack, false))
return false;
- if (recipientStack.StackTypeId == null || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId))
+ if (string.IsNullOrEmpty(recipientStack.StackTypeId) || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId))
return false;
transfered = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack));
if (component.MaxCountOverride != null)
return component.MaxCountOverride.Value;
- if (component.StackTypeId == null)
+ if (string.IsNullOrEmpty(component.StackTypeId))
return 1;
var stackProto = _prototype.Index<StackPrototype>(component.StackTypeId);
return GetMaxCount(component) - component.Count;
}
+ /// <summary>
+ /// Tries to add one stack to another. May have some leftover count in the inserted entity.
+ /// </summary>
+ public bool TryAdd(EntityUid insertEnt, EntityUid targetEnt, StackComponent? insertStack = null, StackComponent? targetStack = null)
+ {
+ if (!Resolve(insertEnt, ref insertStack) || !Resolve(targetEnt, ref targetStack))
+ return false;
+
+ var count = insertStack.Count;
+ return TryAdd(insertEnt, targetEnt, count, insertStack, targetStack);
+ }
+
+ /// <summary>
+ /// Tries to add one stack to another. May have some leftover count in the inserted entity.
+ /// </summary>
+ public bool TryAdd(EntityUid insertEnt, EntityUid targetEnt, int count, StackComponent? insertStack = null, StackComponent? targetStack = null)
+ {
+ if (!Resolve(insertEnt, ref insertStack) || !Resolve(targetEnt, ref targetStack))
+ return false;
+
+ var available = GetAvailableSpace(targetStack);
+
+ if (available <= 0)
+ return false;
+
+ var change = Math.Min(available, count);
+
+ SetCount(targetEnt, targetStack.Count + change, targetStack);
+ SetCount(insertEnt, insertStack.Count - change, insertStack);
+ return true;
+ }
+
private void OnStackStarted(EntityUid uid, StackComponent component, ComponentStartup args)
{
if (!TryComp(uid, out AppearanceComponent? appearance))
sprite: Objects/Materials/ore.rsi
- type: Item
sprite: Objects/Materials/ore.rsi
+ size: 30
- type: ItemStatus
- type: Tag
tags:
stackType: Cable
- type: Sprite
sprite: Objects/Tools/cable-coils.rsi
- netsync: false
- type: Item
sprite: Objects/Tools/cable-coils.rsi
+ size: 10
- type: CablePlacer
- type: Clickable
- type: StaticPrice
- type: Sprite
state: coilhv-30
- type: Item
- size: 10
heldPrefix: coilhv
- type: CablePlacer
cablePrototypeID: CableHV
components:
- type: Sprite
state: coilhv-10
- - type: Item
- size: 3
- type: Stack
count: 1
- type: Sprite
state: coilmv-30
- type: Item
- size: 10
heldPrefix: coilmv
- type: CablePlacer
cablePrototypeID: CableMV
components:
- type: Sprite
state: coilmv-10
- - type: Item
- size: 3
- type: Stack
count: 1
- type: Sprite
state: coillv-30
- type: Item
- size: 10
heldPrefix: coillv
- type: CablePlacer
cablePrototypeID: CableApcExtension
components:
- type: Sprite
state: coillv-10
- - type: Item
- size: 3
- type: Stack
count: 1