[TEST MERGE] Slot-based Storage (#21212)

This commit is contained in:
Nemanja
2023-10-25 18:53:38 -04:00
committed by GitHub
parent 6b04aaf964
commit 41795720da
234 changed files with 1052 additions and 1008 deletions

View File

@@ -264,7 +264,7 @@ public abstract partial class InventorySystem
if (slotDefinition.DependsOn != null && !TryGetSlotEntity(target, slotDefinition.DependsOn, out _, inventory))
return false;
var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) && item is { Size: <= (int) ReferenceSizes.Pocket };
var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) && item is { Size: <= ItemSize.Small };
if (clothing == null && !fittingInPocket
|| clothing != null && !clothing.Slots.HasFlag(slotDefinition.SlotFlags) && !fittingInPocket)
{

View File

@@ -13,18 +13,17 @@ namespace Content.Shared.Item;
[Access(typeof(SharedItemSystem))]
public sealed partial class ItemComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("size")]
[Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)]
public int Size = 5;
[DataField, ViewVariables(VVAccess.ReadWrite)]
[Access(typeof(SharedItemSystem))]
public ItemSize Size = ItemSize.Small;
[Access(typeof(SharedItemSystem))]
[DataField("inhandVisuals")]
[DataField]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
[Access(typeof(SharedItemSystem))]
[ViewVariables(VVAccess.ReadWrite)]
[DataField("heldPrefix")]
[DataField]
public string? HeldPrefix;
/// <summary>
@@ -39,10 +38,10 @@ public sealed partial class ItemComponent : Component
[Serializable, NetSerializable]
public sealed class ItemComponentState : ComponentState
{
public int Size { get; }
public ItemSize Size { get; }
public string? HeldPrefix { get; }
public ItemComponentState(int size, string? heldPrefix)
public ItemComponentState(ItemSize size, string? heldPrefix)
{
Size = size;
HeldPrefix = heldPrefix;
@@ -66,6 +65,43 @@ public sealed class VisualsChangedEvent : EntityEventArgs
}
}
/// <summary>
/// Abstracted sizes for items.
/// Used to determine what can fit into inventories.
/// </summary>
public enum ItemSize
{
/// <summary>
/// Items that can be held completely in one's hand.
/// </summary>
Tiny = 1,
/// <summary>
/// Items that can fit inside of a standard pocket.
/// </summary>
Small = 2,
/// <summary>
/// Items that can fit inside of a standard bag.
/// </summary>
Normal = 3,
/// <summary>
/// Items that are too large to fit inside of standard bags, but can worn in exterior slots or placed in custom containers.
/// </summary>
Large = 4,
/// <summary>
/// Items that are too large to place inside of any kind of container.
/// </summary>
Huge = 5,
/// <summary>
/// Picture furry gf
/// </summary>
Ginormous = 6
}
/// <summary>
/// Reference sizes for common containers and items.
/// </summary>

View File

@@ -1,3 +1,4 @@
using Content.Shared.Item;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
@@ -20,11 +21,11 @@ public sealed partial class ItemToggleComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
[DataField("offSize")]
public int OffSize = 1;
public ItemSize OffSize = ItemSize.Small;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("onSize")]
public int OnSize = 9999;
public ItemSize OnSize = ItemSize.Huge;
}
[ByRefEvent]

View File

@@ -1,23 +1,27 @@
using Content.Shared.CombatMode;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
using Content.Shared.Examine;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Item;
public abstract class SharedItemSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] protected readonly SharedContainerSystem Container = default!;
public const int ItemSizeWeightTiny = 1;
public const int ItemSizeWeightSmall = 2;
public const int ItemSizeWeightNormal = 4;
public const int ItemSizeWeightLarge = 8;
public const int ItemSizeWeightHuge = 16;
public const int ItemSizeWeightGinormous = 32;
public override void Initialize()
{
base.Initialize();
@@ -33,13 +37,13 @@ public abstract class SharedItemSystem : EntitySystem
#region Public API
public void SetSize(EntityUid uid, int size, ItemComponent? component = null)
public void SetSize(EntityUid uid, ItemSize size, ItemComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;
component.Size = size;
Dirty(component);
Dirty(uid, component);
}
public void SetHeldPrefix(EntityUid uid, string? heldPrefix, ItemComponent? component = null)
@@ -51,7 +55,7 @@ public abstract class SharedItemSystem : EntitySystem
return;
component.HeldPrefix = heldPrefix;
Dirty(component);
Dirty(uid, component);
VisualsChanged(uid);
}
@@ -67,7 +71,7 @@ public abstract class SharedItemSystem : EntitySystem
item.InhandVisuals = otherItem.InhandVisuals;
item.HeldPrefix = otherItem.HeldPrefix;
Dirty(item);
Dirty(uid, item);
VisualsChanged(uid);
}
@@ -83,14 +87,7 @@ public abstract class SharedItemSystem : EntitySystem
protected virtual void OnStackCountChanged(EntityUid uid, ItemComponent component, StackCountChangedEvent args)
{
if (!TryComp<StackComponent>(uid, out var stack))
return;
if (!_prototype.TryIndex<StackPrototype>(stack.StackTypeId, out var stackProto) ||
stackProto.ItemSize is not { } size)
return;
SetSize(uid, args.NewCount * size, component);
}
private void OnHandleState(EntityUid uid, ItemComponent component, ref ComponentHandleState args)
@@ -135,7 +132,7 @@ public abstract class SharedItemSystem : EntitySystem
private void OnExamine(EntityUid uid, ItemComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("item-component-on-examine-size",
("size", component.Size)));
("size", GetItemSizeLocale(component.Size))));
}
/// <summary>
@@ -148,4 +145,32 @@ public abstract class SharedItemSystem : EntitySystem
public virtual void VisualsChanged(EntityUid owner)
{
}
[PublicAPI]
public static string GetItemSizeLocale(ItemSize size)
{
return Robust.Shared.Localization.Loc.GetString($"item-component-size-{size.ToString()}");
}
[PublicAPI]
public static int GetItemSizeWeight(ItemSize size)
{
switch (size)
{
case ItemSize.Tiny:
return ItemSizeWeightTiny;
case ItemSize.Small:
return ItemSizeWeightSmall;
case ItemSize.Normal:
return ItemSizeWeightNormal;
case ItemSize.Large:
return ItemSizeWeightLarge;
case ItemSize.Huge:
return ItemSizeWeightHuge;
case ItemSize.Ginormous:
return ItemSizeWeightGinormous;
default:
throw new ArgumentOutOfRangeException(nameof(size), size, null);
}
}
}

View File

@@ -1,10 +1,7 @@
using Content.Server.Storage.Components;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Stacks;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Shared.Storage.EntitySystems;
@@ -56,7 +53,7 @@ public sealed class MagnetPickupSystem : EntitySystem
comp.NextScan += ScanDelay;
// No space
if (storage.StorageUsed >= storage.StorageCapacityMax)
if (!_storage.HasSpace((uid, storage)))
continue;
if (!_inventory.TryGetContainingSlot(uid, out var slotDef))

View File

@@ -4,7 +4,6 @@ using Content.Shared.CombatMode;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Implants.Components;
@@ -46,6 +45,8 @@ public abstract class SharedStorageSystem : EntitySystem
private EntityQuery<StackComponent> _stackQuery;
private EntityQuery<TransformComponent> _xformQuery;
public const ItemSize DefaultStorageMaxItemSize = ItemSize.Normal;
/// <inheritdoc />
public override void Initialize()
{
@@ -89,6 +90,7 @@ public abstract class SharedStorageSystem : EntitySystem
{
// TODO: I had this.
// We can get states being applied before the container is ready.
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (component.Container == default)
return;
@@ -229,7 +231,7 @@ public abstract class SharedStorageSystem : EntitySystem
return;
}
if (TryComp<TransformComponent>(uid, out var transformOwner) && TryComp<TransformComponent>(target, out var transformEnt))
if (_xformQuery.TryGetComponent(uid, out var transformOwner) && TryComp<TransformComponent>(target, out var transformEnt))
{
var parent = transformOwner.ParentUid;
@@ -239,7 +241,7 @@ public abstract class SharedStorageSystem : EntitySystem
_transform
);
if (PlayerInsertEntityInWorld(uid, args.User, target, storageComp))
if (PlayerInsertEntityInWorld((uid, storageComp), args.User, target))
{
RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(GetNetEntity(uid),
new List<NetEntity> { GetNetEntity(target) },
@@ -285,7 +287,7 @@ public abstract class SharedStorageSystem : EntitySystem
var angle = targetXform.LocalRotation;
if (PlayerInsertEntityInWorld(uid, args.Args.User, entity, component))
if (PlayerInsertEntityInWorld((uid, component), args.Args.User, entity))
{
successfullyInserted.Add(entity);
successfullyInsertedPositions.Add(position);
@@ -322,7 +324,7 @@ public abstract class SharedStorageSystem : EntitySystem
/// </summary>
private void OnInteractWithItem(EntityUid uid, StorageComponent storageComp, StorageInteractWithItemEvent args)
{
if (args.Session.AttachedEntity is not EntityUid player)
if (args.Session.AttachedEntity is not { } player)
return;
var entity = GetEntity(args.InteractedItemUID);
@@ -396,27 +398,10 @@ public abstract class SharedStorageSystem : EntitySystem
public void RecalculateStorageUsed(EntityUid uid, StorageComponent storageComp)
{
storageComp.StorageUsed = 0;
foreach (var entity in storageComp.Container.ContainedEntities)
{
if (!_itemQuery.TryGetComponent(entity, out var itemComp))
continue;
var size = itemComp.Size;
storageComp.StorageUsed += size;
}
_appearance.SetData(uid, StorageVisuals.StorageUsed, storageComp.StorageUsed);
_appearance.SetData(uid, StorageVisuals.Capacity, storageComp.StorageCapacityMax);
}
public int GetAvailableSpace(EntityUid uid, StorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return 0;
return component.StorageCapacityMax - component.StorageUsed;
// it might make more sense to use the weights instead of the slots.
// I'm not sure.
_appearance.SetData(uid, StorageVisuals.StorageUsed, storageComp.Container.ContainedEntities.Count);
_appearance.SetData(uid, StorageVisuals.Capacity, storageComp.MaxSlots);
}
/// <summary>
@@ -449,17 +434,20 @@ public abstract class SharedStorageSystem : EntitySystem
/// Verifies if an entity can be stored and if it fits
/// </summary>
/// <param name="uid">The entity to check</param>
/// <param name="insertEnt"></param>
/// <param name="reason">If returning false, the reason displayed to the player</param>
/// <param name="storageComp"></param>
/// <param name="item"></param>
/// <returns>true if it can be inserted, false otherwise</returns>
public bool CanInsert(EntityUid uid, EntityUid insertEnt, out string? reason, StorageComponent? storageComp = null)
public bool CanInsert(EntityUid uid, EntityUid insertEnt, out string? reason, StorageComponent? storageComp = null, ItemComponent? item = null)
{
if (!Resolve(uid, ref storageComp))
if (!Resolve(uid, ref storageComp) || !Resolve(insertEnt, ref item))
{
reason = null;
return false;
}
if (TryComp(insertEnt, out TransformComponent? transformComp) && transformComp.Anchored)
if (Transform(insertEnt).Anchored)
{
reason = "comp-storage-anchored-failure";
return false;
@@ -477,15 +465,19 @@ public abstract class SharedStorageSystem : EntitySystem
return false;
}
if (TryComp(insertEnt, out StorageComponent? storage) &&
storage.StorageCapacityMax >= storageComp.StorageCapacityMax)
if (item.Size > GetMaxItemSize((uid, storageComp)))
{
reason = "comp-storage-too-big";
return false;
}
if (storageComp.Container.ContainedEntities.Count >= storageComp.MaxSlots)
{
reason = "comp-storage-insufficient-capacity";
return false;
}
if (TryComp(insertEnt, out ItemComponent? itemComp) &&
itemComp.Size > storageComp.StorageCapacityMax - storageComp.StorageUsed)
if (SharedItemSystem.GetItemSizeWeight(item.Size) + GetCumulativeItemSizes(uid, storageComp) > GetMaxTotalWeight((uid, storageComp)))
{
reason = "comp-storage-insufficient-capacity";
return false;
@@ -542,8 +534,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (insertStack.Count > 0)
{
// Try to insert it as a new stack.
if (TryComp(insertEnt, out ItemComponent? itemComp) &&
itemComp.Size > storageComp.StorageCapacityMax - storageComp.StorageUsed ||
if (!CanInsert(uid, insertEnt, out _, storageComp) ||
!storageComp.Container.Insert(insertEnt))
{
// If we also didn't do any stack fills above then just end
@@ -568,7 +559,9 @@ public abstract class SharedStorageSystem : EntitySystem
/// <summary>
/// Inserts an entity into storage from the player's active hand
/// </summary>
/// <param name="uid"></param>
/// <param name="player">The player to insert an entity from</param>
/// <param name="storageComp"></param>
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, StorageComponent? storageComp = null)
{
@@ -589,21 +582,23 @@ public abstract class SharedStorageSystem : EntitySystem
return false;
}
return PlayerInsertEntityInWorld(uid, player, toInsert.Value, storageComp);
return PlayerInsertEntityInWorld((uid, storageComp), player, toInsert.Value);
}
/// <summary>
/// Inserts an Entity (<paramref name="toInsert"/>) in the world into storage, informing <paramref name="player"/> if it fails.
/// <paramref name="toInsert"/> is *NOT* held, see <see cref="PlayerInsertHeldEntity(Robust.Shared.GameObjects.EntityUid)"/>.
/// <paramref name="toInsert"/> is *NOT* held, see <see cref="PlayerInsertHeldEntity(EntityUid,EntityUid,StorageComponent)"/>.
/// </summary>
/// <param name="uid"></param>
/// <param name="player">The player to insert an entity with</param>
/// <param name="toInsert"></param>
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertEntityInWorld(EntityUid uid, EntityUid player, EntityUid toInsert, StorageComponent? storageComp = null)
public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert)
{
if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid))
if (!Resolve(uid, ref uid.Comp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid))
return false;
if (!Insert(uid, toInsert, out _, user: player, storageComp))
if (!Insert(uid, toInsert, out _, user: player, uid.Comp))
{
_popupSystem.PopupClient(Loc.GetString("comp-storage-cant-insert"), uid, player);
return false;
@@ -611,6 +606,65 @@ public abstract class SharedStorageSystem : EntitySystem
return true;
}
/// <summary>
/// Returns true if there is enough space to theoretically fit another item.
/// </summary>
public bool HasSpace(Entity<StorageComponent?> uid)
{
if (!Resolve(uid, ref uid.Comp))
return false;
return uid.Comp.Container.ContainedEntities.Count < uid.Comp.MaxSlots &&
GetCumulativeItemSizes(uid, uid.Comp) < GetMaxTotalWeight(uid);
}
/// <summary>
/// Returns the sum of all the ItemSizes of the items inside of a storage.
/// </summary>
public int GetCumulativeItemSizes(EntityUid uid, StorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return 0;
var sum = 0;
foreach (var item in component.Container.ContainedEntities)
{
if (!_itemQuery.TryGetComponent(item, out var itemComp))
continue;
sum += SharedItemSystem.GetItemSizeWeight(itemComp.Size);
}
return sum;
}
public ItemSize GetMaxItemSize(Entity<StorageComponent?> uid)
{
if (!Resolve(uid, ref uid.Comp))
return DefaultStorageMaxItemSize;
// If we specify a max item size, use that
if (uid.Comp.MaxItemSize != null)
return uid.Comp.MaxItemSize.Value;
if (!_itemQuery.TryGetComponent(uid, out var item))
return DefaultStorageMaxItemSize;
// if there is no max item size specified, the value used
// is one below the item size of the storage entity, clamped at ItemSize.Tiny
return (ItemSize) Math.Max((int) item.Size - 1, 1);
}
public int GetMaxTotalWeight(Entity<StorageComponent?> uid)
{
if (!Resolve(uid, ref uid.Comp))
return 0;
if (uid.Comp.MaxTotalWeight != null)
return uid.Comp.MaxTotalWeight.Value;
return uid.Comp.MaxSlots * SharedItemSystem.GetItemSizeWeight(GetMaxItemSize(uid));
}
/// <summary>
/// Plays a clientside pickup animation for the specified uid.
/// </summary>

View File

@@ -1,3 +1,5 @@
using Content.Shared.Item;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
@@ -20,6 +22,25 @@ namespace Content.Shared.Storage
[ViewVariables]
public Container Container = default!;
/// <summary>
/// The max number of entities that can be inserted into this storage.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int MaxSlots = 7;
/// <summary>
/// The maximum size item that can be inserted into this storage,
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[Access(typeof(SharedStorageSystem))]
public ItemSize? MaxItemSize;
/// <summary>
/// A limit for the cumulative ItemSizes that can be inserted in this storage.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int? MaxTotalWeight;
// TODO: Make area insert its own component.
[DataField("quickInsert")]
public bool QuickInsert; // Can insert storables by "attacking" them with the storage entity
@@ -45,18 +66,6 @@ namespace Content.Shared.Storage
[DataField("blacklist")]
public EntityWhitelist? Blacklist;
/// <summary>
/// How much storage is currently being used by contained entities.
/// </summary>
[ViewVariables, DataField("storageUsed"), AutoNetworkedField]
public int StorageUsed;
/// <summary>
/// Maximum capacity for storage.
/// </summary>
[DataField("capacity"), AutoNetworkedField]
public int StorageCapacityMax = 10000;
/// <summary>
/// Sound played whenever an entity is inserted into storage.
/// </summary>