namespace Content.Client.Stack;
+/// <summary>
+/// Used by hands in player UI to display the stack count.
+/// </summary>
public sealed class StackStatusControl : Control
{
private readonly StackComponent _parent;
-using System.Linq;
using Content.Client.Items;
using Content.Client.Storage.Systems;
using Content.Shared.Stacks;
namespace Content.Client.Stack
{
+ /// <inheritdoc />
[UsedImplicitly]
public sealed class StackSystem : SharedStackSystem
{
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent<StackComponent, AppearanceChangeEvent>(OnAppearanceChange);
Subs.ItemStatus<StackComponent>(ent => new StackStatusControl(ent));
}
- public override void SetCount(EntityUid uid, int amount, StackComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- base.SetCount(uid, amount, component);
-
- // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
- if (component.Count <= 0)
- {
- Xform.DetachEntity(uid, Transform(uid));
- return;
- }
-
- component.UiUpdateNeeded = true;
- }
+ #region Appearance
- private void OnAppearanceChange(EntityUid uid, StackComponent comp, ref AppearanceChangeEvent args)
+ private void OnAppearanceChange(Entity<StackComponent> ent, ref AppearanceChangeEvent args)
{
+ var (uid, comp) = ent;
+
if (args.Sprite == null || comp.LayerStates.Count < 1)
return;
- // Skip processing if no actual
+ // Skip processing if no elements in the stack
if (!_appearanceSystem.TryGetData<int>(uid, StackVisuals.Actual, out var actual, args.Component))
return;
ApplyLayerFunction((uid, comp), ref actual, ref maxCount);
if (comp.IsComposite)
- _counterSystem.ProcessCompositeSprite(uid, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite);
+ {
+ _counterSystem.ProcessCompositeSprite(uid,
+ actual,
+ maxCount,
+ comp.LayerStates,
+ hidden,
+ sprite: args.Sprite);
+ }
else
- _counterSystem.ProcessOpaqueSprite(uid, comp.BaseLayer, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite);
+ {
+ _counterSystem.ProcessOpaqueSprite(uid,
+ comp.BaseLayer,
+ actual,
+ maxCount,
+ comp.LayerStates,
+ hidden,
+ sprite: args.Sprite);
+ }
}
/// <summary>
/// <param name="ent">The entity considered.</param>
/// <param name="actual">The actual number of items in the stack. Altered depending on the function to run.</param>
/// <param name="maxCount">The maximum number of items in the stack. Altered depending on the function to run.</param>
- /// <returns>Whether or not a function was applied.</returns>
+ /// <returns>True if a function was applied.</returns>
private bool ApplyLayerFunction(Entity<StackComponent> ent, ref int actual, ref int maxCount)
{
switch (ent.Comp.LayerFunction)
ApplyThreshold(threshold, ref actual, ref maxCount);
return true;
}
+
break;
}
+
// No function applied.
return false;
}
else
break;
}
+
actual = newActual;
}
+
+ #endregion
}
}
[TestPrototypes]
private const string StackProto = @"
-- type: entity
- id: A
-
- type: stack
id: StackProto
name: stack-steel
- spawn: A
+ spawn: StackEnt
- type: entity
id: StackEnt
Assert.That(sys.IsEntityInContainer(shard), Is.True);
Assert.That(sys.IsEntityInContainer(rods), Is.False);
Assert.That(sys.IsEntityInContainer(wires), Is.False);
- Assert.That(rodStack, Has.Count.EqualTo(8));
- Assert.That(wireStack, Has.Count.EqualTo(7));
+ Assert.That(rodStack.Count, Is.EqualTo(8));
+ Assert.That(wireStack.Count, Is.EqualTo(7));
await FindEntity(Spear, shouldSucceed: false);
await Server.WaitPost(() =>
{
uid = SEntMan.SpawnEntity(stackProto.Spawn, coords);
- Stack.SetCount(uid, spec.Quantity);
+ Stack.SetCount((uid, null), spec.Quantity);
});
return uid;
}
await server.WaitPost(() =>
{
var ent = entManager.SpawnEntity(id, testMap.GridCoords);
- stackSys.SetCount(ent, 1);
+ if (entManager.TryGetComponent<StackComponent>(ent, out var stackComp))
+ stackSys.SetCount((ent, stackComp), 1);
priceCache[id] = price = pricing.GetPrice(ent, false);
entManager.DeleteEntity(ent);
});
$"{proto.ID} material has no stack prototype");
if (stackProto != null)
- Assert.That(proto.StackEntity, Is.EqualTo(stackProto.Spawn));
+ Assert.That(proto.StackEntity, Is.EqualTo(stackProto.Spawn.Id));
}
});
// Unbounded intentionally.
_quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-adjust-stack"), Loc.GetString("admin-verbs-dialog-adjust-stack-amount", ("max", _stackSystem.GetMaxCount(stack))), (int newAmount) =>
{
- _stackSystem.SetCount(args.Target, newAmount, stack);
+ _stackSystem.SetCount((args.Target, stack), newAmount);
});
},
Impact = LogImpact.Medium,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill-stack.png")),
Act = () =>
{
- _stackSystem.SetCount(args.Target, _stackSystem.GetMaxCount(stack), stack);
+ _stackSystem.SetCount((args.Target, stack), _stackSystem.GetMaxCount(stack));
},
Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-fill-stack-description"),
if (args.Account == null)
{
var stackPrototype = _protoMan.Index(ent.Comp.CashType);
- _stack.Spawn(args.Amount, stackPrototype, Transform(ent).Coordinates);
+ _stack.SpawnAtPosition(args.Amount, stackPrototype, Transform(ent).Coordinates);
if (!_emag.CheckFlag(ent, EmagType.Interaction))
{
{
// if the clone is a stack as well, adjust the count of the copy
if (TryComp<StackComponent>(args.CloneUid, out var cloneStackComp))
- _stack.SetCount(args.CloneUid, ent.Comp.Count, cloneStackComp);
+ _stack.SetCount((args.CloneUid, cloneStackComp), ent.Comp.Count);
}
private void OnCloneItemLabel(Entity<LabelComponent> ent, ref CloningItemEvent args)
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
{
var stackSystem = entityManager.EntitySysManager.GetEntitySystem<StackSystem>();
- var stacks = stackSystem.SpawnMultiple(Prototype, Amount, userUid ?? uid);
+ var stacks = stackSystem.SpawnMultipleNextToOrDrop(Prototype, Amount, userUid ?? uid);
if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp))
return;
foreach (var item in stacks)
{
- stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp);
+ stackSystem.TryMergeToHands(item, (userUid.Value, handsComp));
}
}
else
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
{
- entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(uid, Amount);
+ entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount((uid, null), Amount);
}
}
}
{
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
var stack = entityManager.GetComponent<StackComponent>(stackEnt);
- entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(stackEnt, Amount, stack);
+ entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount((stackEnt, stack), Amount);
}
else
{
// TODO allow taking from several stacks.
// Also update crafting steps to check if it works.
- var splitStack = _stackSystem.Split(entity, materialStep.Amount, user.ToCoordinates(0, 0), stack);
+ var splitStack = _stackSystem.Split((entity, stack), materialStep.Amount, user.ToCoordinates(0, 0));
if (splitStack == null)
continue;
foreach (var (stackType, amount) in machineBoard.StackRequirements)
{
- var stack = _stackSystem.Spawn(amount, stackType, xform.Coordinates);
+ var stack = _stackSystem.SpawnAtPosition(amount, stackType, xform.Coordinates);
if (!_container.Insert(stack, partContainer))
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
}
return true;
}
- var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack);
+ var splitStack = _stack.Split((used, stack), needed, Transform(uid).Coordinates);
if (splitStack == null)
return false;
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.EntityManager.ComponentFactory))
{
var spawned = system.EntityManager.SpawnEntity(entityId, xform.Coordinates.Offset(system.Random.NextVector2(-Offset, Offset)));
- system.StackSystem.SetCount(spawned, toSpawn);
+ system.StackSystem.SetCount((spawned, null), toSpawn);
system.EntityManager.GetComponent<TransformComponent>(spawned).LocalRotation = system.Random.NextAngle();
}
else
var spawned = SpawnInContainer
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
: system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
- system.StackSystem.SetCount(spawned, count);
+ system.StackSystem.SetCount((spawned, null), count);
TransferForensics(spawned, system, owner);
}
if (component.Deleted || !IsTileClear())
return;
- if (TryComp(uid, out StackComponent? stackComp)
- && component.RemoveOnInteract && !_stackSystem.Use(uid, 1, stackComp))
+ if (TryComp<StackComponent>(uid, out var stackComp)
+ && component.RemoveOnInteract && !_stackSystem.TryUse((uid, stackComp), 1))
{
return;
}
if (TryComp(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
{
- var splitStack = _stackSystem.Split(throwEnt.Value, 1, Comp<TransformComponent>(player).Coordinates, stack);
+ var splitStack = _stackSystem.Split((throwEnt.Value, stack), 1, Comp<TransformComponent>(player).Coordinates);
if (splitStack is not {Valid: true})
return false;
// If an entity has a stack component, use the stacktype instead of prototype id
if (TryComp<StackComponent>(item, out var stackComp))
{
- itemID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
+ itemID = _prototype.Index(stackComp.StackTypeId).Spawn;
}
else
{
{
_container.Remove(item, component.Storage);
}
- _stack.Use(item, 1, stackComp);
+ _stack.ReduceCount((item, stackComp), 1);
break;
}
else
scaledSolution.ScaleSolution(fitsCount);
solution = scaledSolution;
- _stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel
+ _stackSystem.ReduceCount((item, stack), fitsCount); // Setting to 0 will QueueDel
}
else
{
component.StateExpiryTime = (float)component.RefuelMaterialTime.TotalSeconds;
_nameModifier.RefreshNameModifiers(uid);
- _stackSystem.SetCount(args.Used, stack.Count - 1, stack);
+ _stackSystem.ReduceCount((args.Used, stack), 1);
UpdateVisualizer((uid, component));
return;
}
component.StateExpiryTime += (float)component.RefuelMaterialTime.TotalSeconds;
- _stackSystem.SetCount(args.Used, stack.Count - 1, stack);
+ _stackSystem.ReduceCount((args.Used, stack), 1);
args.Handled = true;
}
return;
var volumePerSheet = composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == msg.Material).Value;
- var sheetsToExtract = Math.Min(msg.SheetsToExtract, _stackSystem.GetMaxCount(material.StackEntity));
+ var sheetsToExtract = Math.Min(msg.SheetsToExtract, _stackSystem.GetMaxCount(material.StackEntity.Value));
volume = sheetsToExtract * volumePerSheet;
}
if (amountToSpawn == 0)
return new List<EntityUid>();
- return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
+ return _stackSystem.SpawnMultipleAtPosition(materialProto.StackEntity.Value, amountToSpawn, coordinates);
}
/// <summary>
return;
}
- if (TryComp<StackComponent>(placer, out var stack) && !_stack.Use(placer, 1, stack))
+ if (TryComp<StackComponent>(placer, out var stack) && !_stack.TryUse((placer.Owner, stack), 1))
return;
var newCable = Spawn(component.CablePrototypeId, _map.GridTileToLocal(gridUid, grid, snapPos));
using Content.Shared.Popups;
using Content.Shared.Stacks;
-using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server.Stack
{
/// <summary>
- /// Entity system that handles everything relating to stacks.
- /// This is a good example for learning how to code in an ECS manner.
+ /// Entity system that handles everything relating to stacks.
+ /// This is a good example for learning how to code in an ECS manner.
/// </summary>
[UsedImplicitly]
public sealed class StackSystem : SharedStackSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- public override void Initialize()
- {
- base.Initialize();
- }
-
- public override void SetCount(EntityUid uid, int amount, StackComponent? component = null)
- {
- if (!Resolve(uid, ref component, false))
- return;
-
- base.SetCount(uid, amount, component);
-
- // Queue delete stack if count reaches zero.
- if (component.Count <= 0)
- QueueDel(uid);
- }
+ #region Spawning
/// <summary>
- /// Try to split this stack into two. Returns a non-null <see cref="Robust.Shared.GameObjects.EntityUid"/> if successful.
+ /// Spawns a new entity and moves an amount to it from the stack.
+ /// Moves nothing if amount is greater than ent's stack count.
/// </summary>
- public EntityUid? Split(EntityUid uid, int amount, EntityCoordinates spawnPosition, StackComponent? stack = null)
+ /// <param name="amount"> How much to move to the new entity. </param>
+ /// <returns>Null if StackComponent doesn't resolve, or amount to move is greater than ent has available.</returns>
+ [PublicAPI]
+ public EntityUid? Split(Entity<StackComponent?> ent, int amount, EntityCoordinates spawnPosition)
{
- if (!Resolve(uid, ref stack))
+ if (!Resolve(ent.Owner, ref ent.Comp))
return null;
// Try to remove the amount of things we want to split from the original stack...
- if (!Use(uid, amount, stack))
+ if (!TryUse(ent, amount))
return null;
- // Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked...
- var prototype = _prototypeManager.TryIndex<StackPrototype>(stack.StackTypeId, out var stackType)
- ? stackType.Spawn.ToString()
- : Prototype(uid)?.ID;
+ if (!_prototypeManager.Resolve(ent.Comp.StackTypeId, out var stackType))
+ return null;
// Set the output parameter in the event instance to the newly split stack.
- var entity = Spawn(prototype, spawnPosition);
+ var newEntity = SpawnAtPosition(stackType.Spawn, spawnPosition);
- if (TryComp(entity, out StackComponent? stackComp))
- {
- // Set the split stack's count.
- SetCount(entity, amount, stackComp);
- // Don't let people dupe unlimited stacks
- stackComp.Unlimited = false;
- }
+ // There should always be a StackComponent
+ var stackComp = Comp<StackComponent>(newEntity);
- var ev = new StackSplitEvent(entity);
- RaiseLocalEvent(uid, ref ev);
+ SetCount((newEntity, stackComp), amount);
+ stackComp.Unlimited = false; // Don't let people dupe unlimited stacks
+ Dirty(newEntity, stackComp);
- return entity;
- }
+ var ev = new StackSplitEvent(newEntity);
+ RaiseLocalEvent(ent, ref ev);
- /// <summary>
- /// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
- /// </summary>
- public EntityUid Spawn(int amount, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
- {
- var proto = _prototypeManager.Index(id);
- return Spawn(amount, proto, spawnPosition);
+ return newEntity;
}
+ #region SpawnAtPosition
+
/// <summary>
- /// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
+ /// Spawns a stack of a certain stack type and sets its count. Won't set the stack over its max.
/// </summary>
- public EntityUid Spawn(int amount, StackPrototype prototype, EntityCoordinates spawnPosition)
+ /// <param name="count">The amount to set the spawned stack to.</param>
+ [PublicAPI]
+ public EntityUid SpawnAtPosition(int count, StackPrototype prototype, EntityCoordinates spawnPosition)
{
- // Set the output result parameter to the new stack entity...
- var entity = SpawnAtPosition(prototype.Spawn, spawnPosition);
- var stack = Comp<StackComponent>(entity);
+ var entity = SpawnAtPosition(prototype.Spawn, spawnPosition); // The real SpawnAtPosition
- // And finally, set the correct amount!
- SetCount(entity, amount, stack);
+ SetCount((entity, null), count);
return entity;
}
+ /// <inheritdoc cref="SpawnAtPosition(int, StackPrototype, EntityCoordinates)"/>
+ [PublicAPI]
+ public EntityUid SpawnAtPosition(int count, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
+ {
+ var proto = _prototypeManager.Index(id);
+ return SpawnAtPosition(count, proto, spawnPosition);
+ }
+
/// <summary>
- /// Say you want to spawn 97 units of something that has a max stack count of 30.
- /// This would spawn 3 stacks of 30 and 1 stack of 7.
+ /// Say you want to spawn 97 units of something that has a max stack count of 30.
+ /// This would spawn 3 stacks of 30 and 1 stack of 7.
/// </summary>
- public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition)
+ /// <returns>The entities spawned.</returns>
+ /// <remarks> If the entity to spawn doesn't have stack component this will spawn a bunch of single items. </remarks>
+ private List<EntityUid> SpawnMultipleAtPosition(EntProtoId entityPrototype,
+ List<int> amounts,
+ EntityCoordinates spawnPosition)
{
- if (amount <= 0)
+ if (amounts.Count <= 0)
{
Log.Error(
- $"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
+ $"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
return new();
}
- var spawns = CalculateSpawns(entityPrototype, amount);
-
var spawnedEnts = new List<EntityUid>();
- foreach (var count in spawns)
+ foreach (var count in amounts)
{
- var entity = SpawnAtPosition(entityPrototype, spawnPosition);
+ var entity = SpawnAtPosition(entityPrototype, spawnPosition); // The real SpawnAtPosition
spawnedEnts.Add(entity);
- SetCount(entity, count);
+ if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
+ SetCount((entity, stackComp), count);
}
return spawnedEnts;
}
- /// <inheritdoc cref="SpawnMultiple(string,int,EntityCoordinates)"/>
- public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityUid target)
+ /// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleAtPosition(EntProtoId entityPrototypeId,
+ int amount,
+ EntityCoordinates spawnPosition)
{
- if (amount <= 0)
+ return SpawnMultipleAtPosition(entityPrototypeId,
+ CalculateSpawns(entityPrototypeId, amount),
+ spawnPosition);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleAtPosition(EntityPrototype entityProto,
+ int amount,
+ EntityCoordinates spawnPosition)
+ {
+ return SpawnMultipleAtPosition(entityProto.ID,
+ CalculateSpawns(entityProto, amount),
+ spawnPosition);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleAtPosition(StackPrototype stack,
+ int amount,
+ EntityCoordinates spawnPosition)
+ {
+ return SpawnMultipleAtPosition(stack.Spawn,
+ CalculateSpawns(stack, amount),
+ spawnPosition);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleAtPosition(ProtoId<StackPrototype> stackId,
+ int amount,
+ EntityCoordinates spawnPosition)
+ {
+ var stackProto = _prototypeManager.Index(stackId);
+ return SpawnMultipleAtPosition(stackProto.Spawn,
+ CalculateSpawns(stackProto, amount),
+ spawnPosition);
+ }
+
+ #endregion
+ #region SpawnNextToOrDrop
+
+ /// <inheritdoc cref="SpawnAtPosition(int, StackPrototype, EntityCoordinates)"/>
+ [PublicAPI]
+ public EntityUid SpawnNextToOrDrop(int amount, StackPrototype prototype, EntityUid source)
+ {
+ var entity = SpawnNextToOrDrop(prototype.Spawn, source); // The real SpawnNextToOrDrop
+ SetCount((entity, null), amount);
+ return entity;
+ }
+
+ /// <inheritdoc cref="SpawnNextToOrDrop(int, StackPrototype, EntityUid)"/>
+ [PublicAPI]
+ public EntityUid SpawnNextToOrDrop(int amount, ProtoId<StackPrototype> id, EntityUid source)
+ {
+ var proto = _prototypeManager.Index(id);
+ return SpawnNextToOrDrop(amount, proto, source);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
+ private List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId entityPrototype,
+ List<int> amounts,
+ EntityUid target)
+ {
+ if (amounts.Count <= 0)
{
Log.Error(
- $"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
+ $"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
return new();
}
- var spawns = CalculateSpawns(entityPrototype, amount);
-
var spawnedEnts = new List<EntityUid>();
- foreach (var count in spawns)
+ foreach (var count in amounts)
{
- var entity = SpawnNextToOrDrop(entityPrototype, target);
+ var entity = SpawnNextToOrDrop(entityPrototype, target); // The real SpawnNextToOrDrop
spawnedEnts.Add(entity);
- SetCount(entity, count);
+ if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
+ SetCount((entity, stackComp), count);
}
return spawnedEnts;
}
+ /// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId stack,
+ int amount,
+ EntityUid target)
+ {
+ return SpawnMultipleNextToOrDrop(stack,
+ CalculateSpawns(stack, amount),
+ target);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleNextToOrDrop(EntityPrototype stack,
+ int amount,
+ EntityUid target)
+ {
+ return SpawnMultipleNextToOrDrop(stack.ID,
+ CalculateSpawns(stack, amount),
+ target);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleNextToOrDrop(StackPrototype stack,
+ int amount,
+ EntityUid target)
+ {
+ return SpawnMultipleNextToOrDrop(stack.Spawn,
+ CalculateSpawns(stack, amount),
+ target);
+ }
+
+ /// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
+ [PublicAPI]
+ public List<EntityUid> SpawnMultipleNextToOrDrop(ProtoId<StackPrototype> stackId,
+ int amount,
+ EntityUid target)
+ {
+ var stackProto = _prototypeManager.Index(stackId);
+ return SpawnMultipleNextToOrDrop(stackProto.Spawn,
+ CalculateSpawns(stackProto, amount),
+ target);
+ }
+
+ #endregion
+ #region Calculate
+
/// <summary>
/// Calculates how many stacks to spawn that total up to <paramref name="amount"/>.
/// </summary>
- /// <param name="entityPrototype">The stack to spawn.</param>
- /// <param name="amount">The amount of pieces across all stacks.</param>
/// <returns>The list of stack counts per entity.</returns>
- private List<int> CalculateSpawns(string entityPrototype, int amount)
+ private List<int> CalculateSpawns(int maxCountPerStack, int amount)
{
- var proto = _prototypeManager.Index<EntityPrototype>(entityPrototype);
- proto.TryGetComponent<StackComponent>(out var stack, EntityManager.ComponentFactory);
- var maxCountPerStack = GetMaxCount(stack);
var amounts = new List<int>();
while (amount > 0)
{
return amounts;
}
- protected override void UserSplit(EntityUid uid, EntityUid userUid, int amount,
- StackComponent? stack = null,
- TransformComponent? userTransform = null)
+ /// <inheritdoc cref="CalculateSpawns(int, int)"/>
+ private List<int> CalculateSpawns(StackPrototype stackProto, int amount)
{
- if (!Resolve(uid, ref stack))
- return;
+ return CalculateSpawns(GetMaxCount(stackProto), amount);
+ }
- if (!Resolve(userUid, ref userTransform))
+ /// <inheritdoc cref="CalculateSpawns(int, int)"/>
+ private List<int> CalculateSpawns(EntityPrototype entityPrototype, int amount)
+ {
+ return CalculateSpawns(GetMaxCount(entityPrototype), amount);
+ }
+
+ /// <inheritdoc cref="CalculateSpawns(int, int)"/>
+ private List<int> CalculateSpawns(EntProtoId entityId, int amount)
+ {
+ return CalculateSpawns(GetMaxCount(entityId), amount);
+ }
+
+ #endregion
+ #endregion
+ #region Event Handlers
+
+ /// <inheritdoc />
+ protected override void UserSplit(Entity<StackComponent> stack, Entity<TransformComponent?> user, int amount)
+ {
+ if (!Resolve(user.Owner, ref user.Comp, false))
return;
if (amount <= 0)
{
- Popup.PopupCursor(Loc.GetString("comp-stack-split-too-small"), userUid, PopupType.Medium);
+ Popup.PopupCursor(Loc.GetString("comp-stack-split-too-small"), user.Owner, PopupType.Medium);
return;
}
- if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split)
+ if (Split(stack.AsNullable(), amount, user.Comp.Coordinates) is not { } split)
return;
- Hands.PickupOrDrop(userUid, split);
+ Hands.PickupOrDrop(user.Owner, split);
- Popup.PopupCursor(Loc.GetString("comp-stack-split"), userUid);
+ Popup.PopupCursor(Loc.GetString("comp-stack-split"), user.Owner);
}
+ #endregion
}
}
{
var cashId = proto.Cash[value];
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value));
- var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coordinates);
+ var ents = _stack.SpawnMultipleAtPosition(cashId, amountToSpawn, coordinates);
if (ents.FirstOrDefault() is {} ent)
_hands.PickupOrDrop(buyer, ent);
amountRemaining -= value * amountToSpawn;
// same tick
currency.Comp.Price.Clear();
if (stack != null)
- _stack.SetCount(currency.Owner, 0, stack);
+ _stack.SetCount((currency.Owner, stack), 0);
QueueDel(currency);
return true;
if (_whitelistSystem.IsWhitelistPass(crusher.CrushingWhitelist, contained))
{
var amount = _random.Next(crusher.MinFragments, crusher.MaxFragments);
- var stacks = _stack.SpawnMultiple(crusher.FragmentStackProtoId, amount, coords);
+ var stacks = _stack.SpawnMultipleAtPosition(crusher.FragmentStackProtoId, amount, coords);
foreach (var stack in stacks)
{
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
var dontRepeat = false;
if (TryComp<StackComponent>(args.Used.Value, out var stackComp))
{
- _stacks.Use(args.Used.Value, 1, stackComp);
+ _stacks.ReduceCount((args.Used.Value, stackComp), 1);
- if (_stacks.GetCount(args.Used.Value, stackComp) <= 0)
+ if (_stacks.GetCount((args.Used.Value, stackComp)) <= 0)
dontRepeat = true;
}
else
if (args.Cancelled || args.Target == null || !TryComp<FultonComponent>(args.Used, out var fulton))
return;
- if (!_stack.Use(args.Used.Value, 1))
+ if (!_stack.TryUse(args.Used.Value, 1))
{
return;
}
--- /dev/null
+using Content.Shared.Hands.Components;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Stacks;
+
+// Partial for public API functions.
+public abstract partial class SharedStackSystem
+{
+ #region Merge Stacks
+
+ /// <summary>
+ /// Moves as much stack count as we can from the donor to the recipient.
+ /// Deletes the donor if count goes to 0.
+ /// </summary>
+ /// <param name="transferred">How much stack count was moved.</param>
+ /// <param name="amount">Optional. Limits amount of stack count to move from the donor.</param>
+ /// <returns> True if transferred is greater than 0. </returns>
+ [PublicAPI]
+ public bool TryMergeStacks(Entity<StackComponent?> donor,
+ Entity<StackComponent?> recipient,
+ out int transferred,
+ int? amount = null)
+ {
+ transferred = 0;
+
+ if (donor == recipient)
+ return false;
+
+ if (!Resolve(recipient, ref recipient.Comp, false) || !Resolve(donor, ref donor.Comp, false))
+ return false;
+
+ if (recipient.Comp.StackTypeId != donor.Comp.StackTypeId)
+ return false;
+
+ // The most we can transfer
+ transferred = Math.Min(donor.Comp.Count, GetAvailableSpace(recipient.Comp));
+ if (transferred <= 0)
+ return false;
+
+ // transfer only as much as we want
+ if (amount > 0)
+ transferred = Math.Min(transferred, amount.Value);
+
+ SetCount(donor, donor.Comp.Count - transferred);
+ SetCount(recipient, recipient.Comp.Count + transferred);
+ return true;
+ }
+
+ /// <summary>
+ /// If the given item is a stack, this attempts to find a matching stack in the users hand and merge with that.
+ /// </summary>
+ /// <remarks>
+ /// If the interaction fails to fully merge the stack, or if this is just not a stack, it will instead try
+ /// to place it in the user's hand normally.
+ /// </remarks>
+ [PublicAPI]
+ public void TryMergeToHands(Entity<StackComponent?> item, Entity<HandsComponent?> user)
+ {
+ if (!Resolve(user.Owner, ref user.Comp, false))
+ return;
+
+ if (!Resolve(item.Owner, ref item.Comp, false))
+ {
+ // This isn't even a stack. Just try to pickup as normal.
+ Hands.PickupOrDrop(user.Owner, item.Owner, handsComp: user.Comp);
+ return;
+ }
+
+ foreach (var held in Hands.EnumerateHeld(user))
+ {
+ TryMergeStacks(item, held, out _);
+
+ if (item.Comp.Count == 0)
+ return;
+ }
+
+ Hands.PickupOrDrop(user.Owner, item.Owner, handsComp: user.Comp);
+ }
+
+ /// <summary>
+ /// Donor entity merges stack count into contacting entities.
+ /// Deletes the donor if count goes to 0.
+ /// </summary>
+ /// <returns> True if donor moved any count to contacts. </returns>
+ [PublicAPI]
+ public bool TryMergeToContacts(Entity<StackComponent?, TransformComponent?> donor)
+ {
+ var (uid, stack, xform) = donor; // sue me
+ if (!Resolve(uid, ref stack, ref xform, false))
+ return false;
+
+ var map = xform.MapID;
+ var bounds = _physics.GetWorldAABB(uid);
+ var intersecting = new HashSet<Entity<StackComponent>>(); // Should we reuse a HashSet instead of making a new one?
+ _entityLookup.GetEntitiesIntersecting(map, bounds, intersecting, LookupFlags.Dynamic | LookupFlags.Sundries);
+
+ var merged = false;
+ foreach (var recipientStack in intersecting)
+ {
+ var otherEnt = recipientStack.Owner;
+ // if you merge a ton of stacks together, you will end up deleting a few by accident.
+ if (TerminatingOrDeleted(otherEnt) || EntityManager.IsQueuedForDeletion(otherEnt))
+ continue;
+
+ if (!TryMergeStacks((uid, stack), recipientStack.AsNullable(), out _))
+ continue;
+ merged = true;
+
+ if (stack.Count <= 0)
+ break;
+ }
+ return merged;
+ }
+
+ #endregion
+ #region Setters
+
+ /// <summary>
+ /// Sets a stack count to an amount. Server will delete ent if count is 0.
+ /// Clamps between zero and the stack's max size.
+ /// </summary>
+ /// <remarks> All setter functions should end up here. </remarks>
+ public void SetCount(Entity<StackComponent?> ent, int amount)
+ {
+ if (!Resolve(ent.Owner, ref ent.Comp))
+ return;
+
+ // Do nothing if amount is already the same.
+ if (amount == ent.Comp.Count)
+ return;
+
+ // Store old value for event-raising purposes...
+ var old = ent.Comp.Count;
+
+ // Clamp the value.
+ amount = Math.Min(amount, GetMaxCount(ent.Comp));
+ amount = Math.Max(amount, 0);
+
+ ent.Comp.Count = amount;
+ ent.Comp.UiUpdateNeeded = true;
+ Dirty(ent);
+
+ Appearance.SetData(ent.Owner, StackVisuals.Actual, ent.Comp.Count);
+ RaiseLocalEvent(ent.Owner, new StackCountChangedEvent(old, ent.Comp.Count));
+
+ // Queue delete stack if count reaches zero.
+ if (ent.Comp.Count <= 0)
+ PredictedQueueDel(ent.Owner);
+ }
+
+ /// <inheritdoc cref="SetCount(Entity{StackComponent?}, int)"/>
+ [Obsolete("Use Entity<T> method instead")]
+ public void SetCount(EntityUid uid, int amount, StackComponent? component = null)
+ {
+ SetCount((uid, component), amount);
+ }
+
+ // TODO
+ /// <summary>
+ /// Increase a stack count by an amount, and spawn new entities if above the max.
+ /// </summary>
+ // public List<EntityUid> RaiseCountAndSpawn(Entity<StackComponent?> ent, int amount);
+
+ /// <summary>
+ /// Reduce a stack count by an amount, even if it would go below 0.
+ /// If it reaches 0 the stack will despawn.
+ /// </summary>
+ /// <seealso cref="TryUse"/>
+ [PublicAPI]
+ public void ReduceCount(Entity<StackComponent?> ent, int amount)
+ {
+ if (!Resolve(ent.Owner, ref ent.Comp))
+ return;
+
+ // Don't reduce unlimited stacks
+ if (ent.Comp.Unlimited)
+ return;
+
+ SetCount(ent, ent.Comp.Count - amount);
+ }
+
+ /// <summary>
+ /// Try to reduce a stack count by a whole amount.
+ /// Won't reduce the stack count if the amount is larger than the stack.
+ /// </summary>
+ /// <returns> True if the count was lowered. Always true if the stack is unlimited. </returns>
+ [PublicAPI]
+ public bool TryUse(Entity<StackComponent?> ent, int amount)
+ {
+ if (!Resolve(ent.Owner, ref ent.Comp))
+ return false;
+
+ // We're unlimited and always greater than amount
+ if (ent.Comp.Unlimited)
+ return true;
+
+ // Check if we have enough things in the stack for this...
+ if (amount > ent.Comp.Count)
+ return false;
+
+ // We do have enough things in the stack, so remove them and change.
+ SetCount(ent, ent.Comp.Count - amount);
+ return true;
+ }
+
+ #endregion
+ #region Getters
+
+ /// <summary>
+ /// Gets the count in a stack. If it cannot be stacked, returns 1.
+ /// </summary>
+ [PublicAPI]
+ public int GetCount(Entity<StackComponent?> ent)
+ {
+ return Resolve(ent.Owner, ref ent.Comp, false) ? ent.Comp.Count : 1;
+ }
+
+ /// <summary>
+ /// Gets the maximum amount that can be fit on a stack.
+ /// </summary>
+ /// <remarks>
+ /// <p>
+ /// if there's no StackComponent, this equals 1. Otherwise, if there's a max
+ /// count override, it equals that. It then checks for a max count value
+ /// on the stack prototype. If there isn't one, it defaults to the max integer
+ /// value (unlimited).
+ /// </p>
+ /// </remarks>
+ [PublicAPI]
+ public int GetMaxCount(StackComponent? component)
+ {
+ if (component == null)
+ return 1;
+
+ if (component.MaxCountOverride != null)
+ return component.MaxCountOverride.Value;
+
+ var stackProto = _prototype.Index(component.StackTypeId);
+ return stackProto.MaxCount ?? int.MaxValue;
+ }
+
+ /// <inheritdoc cref="GetMaxCount(StackComponent?)"/>
+ [PublicAPI]
+ public int GetMaxCount(EntProtoId entityId)
+ {
+ var entProto = _prototype.Index<EntityPrototype>(entityId);
+ entProto.TryGetComponent<StackComponent>(out var stackComp, EntityManager.ComponentFactory);
+ return GetMaxCount(stackComp);
+ }
+
+ /// <inheritdoc cref="GetMaxCount(StackComponent?)"/>
+ [PublicAPI]
+ public int GetMaxCount(EntityPrototype entityId)
+ {
+ entityId.TryGetComponent<StackComponent>(out var stackComp, EntityManager.ComponentFactory);
+ return GetMaxCount(stackComp);
+ }
+
+ /// <inheritdoc cref="GetMaxCount(StackComponent?)"/>
+ [PublicAPI]
+ public int GetMaxCount(EntityUid uid)
+ {
+ return GetMaxCount(CompOrNull<StackComponent>(uid));
+ }
+
+ /// <summary>
+ /// Gets the maximum amount that can be fit on a stack, or int.MaxValue if no max value exists.
+ /// </summary>
+ [PublicAPI]
+ public static int GetMaxCount(StackPrototype stack)
+ {
+ return stack.MaxCount ?? int.MaxValue;
+ }
+
+ /// <inheritdoc cref="GetMaxCount(StackPrototype)"/>
+ [PublicAPI]
+ public int GetMaxCount(ProtoId<StackPrototype> stackId)
+ {
+ return GetMaxCount(_prototype.Index(stackId));
+ }
+
+ /// <summary>
+ /// Gets the remaining space in a stack.
+ /// </summary>
+ [PublicAPI]
+ public int GetAvailableSpace(StackComponent component)
+ {
+ return GetMaxCount(component) - component.Count;
+ }
+
+ #endregion
+}
using System.Numerics;
using Content.Shared.Examine;
-using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Nutrition;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Systems;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
-namespace Content.Shared.Stacks
+namespace Content.Shared.Stacks;
+
+// Partial for general system code and event handlers.
+/// <summary>
+/// System for handling entities which represent a stack of identical items, usually materials.
+/// </summary>
+[UsedImplicitly]
+public abstract partial class SharedStackSystem : EntitySystem
{
- [UsedImplicitly]
- public abstract class SharedStackSystem : EntitySystem
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IViewVariablesManager _vvm = default!;
+ [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] protected readonly SharedHandsSystem Hands = default!;
+ [Dependency] protected readonly SharedTransformSystem Xform = default!;
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] protected readonly SharedPopupSystem Popup = default!;
+ [Dependency] private readonly SharedStorageSystem _storage = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ // TODO: These should be in the prototype.
+ public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
+
+ public override void Initialize()
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly IViewVariablesManager _vvm = default!;
- [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
- [Dependency] protected readonly SharedHandsSystem Hands = default!;
- [Dependency] protected readonly SharedTransformSystem Xform = default!;
- [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- [Dependency] protected readonly SharedPopupSystem Popup = default!;
- [Dependency] private readonly SharedStorageSystem _storage = default!;
-
- public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<StackComponent, ComponentGetState>(OnStackGetState);
- SubscribeLocalEvent<StackComponent, ComponentHandleState>(OnStackHandleState);
- SubscribeLocalEvent<StackComponent, ComponentStartup>(OnStackStarted);
- SubscribeLocalEvent<StackComponent, ExaminedEvent>(OnStackExamined);
- SubscribeLocalEvent<StackComponent, InteractUsingEvent>(OnStackInteractUsing);
- SubscribeLocalEvent<StackComponent, BeforeIngestedEvent>(OnBeforeEaten);
- SubscribeLocalEvent<StackComponent, IngestedEvent>(OnEaten);
- SubscribeLocalEvent<StackComponent, GetVerbsEvent<AlternativeVerb>>(OnStackAlternativeInteract);
-
- _vvm.GetTypeHandler<StackComponent>()
- .AddPath(nameof(StackComponent.Count), (_, comp) => comp.Count, SetCount);
- }
-
- public override void Shutdown()
- {
- base.Shutdown();
-
- _vvm.GetTypeHandler<StackComponent>()
- .RemovePath(nameof(StackComponent.Count));
- }
-
- private void OnStackInteractUsing(EntityUid uid, StackComponent stack, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
-
- if (!TryComp(args.Used, out StackComponent? recipientStack))
- return;
-
- var localRotation = Transform(args.Used).LocalRotation;
-
- if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack))
- return;
-
- args.Handled = true;
-
- // interaction is done, the rest is just generating a pop-up
-
- if (!_gameTiming.IsFirstTimePredicted)
- return;
-
- var popupPos = args.ClickLocation;
- var userCoords = Transform(args.User).Coordinates;
-
- if (!popupPos.IsValid(EntityManager))
- {
- popupPos = userCoords;
- }
-
- switch (transfered)
- {
- case > 0:
- Popup.PopupCoordinates($"+{transfered}", popupPos, Filter.Local(), false);
-
- if (GetAvailableSpace(recipientStack) == 0)
- {
- Popup.PopupCoordinates(Loc.GetString("comp-stack-becomes-full"),
- popupPos.Offset(new Vector2(0, -0.5f)), Filter.Local(), false);
- }
-
- break;
-
- case 0 when GetAvailableSpace(recipientStack) == 0:
- Popup.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local(), false);
- break;
- }
-
- _storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
- }
-
- private bool TryMergeStacks(
- EntityUid donor,
- EntityUid recipient,
- out int transferred,
- StackComponent? donorStack = null,
- StackComponent? recipientStack = null)
- {
- transferred = 0;
- if (donor == recipient)
- return false;
-
- if (!Resolve(recipient, ref recipientStack, false) || !Resolve(donor, ref donorStack, false))
- return false;
-
- if (string.IsNullOrEmpty(recipientStack.StackTypeId) || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId))
- return false;
+ base.Initialize();
- transferred = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack));
- SetCount(donor, donorStack.Count - transferred, donorStack);
- SetCount(recipient, recipientStack.Count + transferred, recipientStack);
- return transferred > 0;
- }
-
- /// <summary>
- /// If the given item is a stack, this attempts to find a matching stack in the users hand, and merge with that.
- /// </summary>
- /// <remarks>
- /// If the interaction fails to fully merge the stack, or if this is just not a stack, it will instead try
- /// to place it in the user's hand normally.
- /// </remarks>
- public void TryMergeToHands(
- EntityUid item,
- EntityUid user,
- StackComponent? itemStack = null,
- HandsComponent? hands = null)
- {
- if (!Resolve(user, ref hands, false))
- return;
-
- if (!Resolve(item, ref itemStack, false))
- {
- // This isn't even a stack. Just try to pickup as normal.
- Hands.PickupOrDrop(user, item, handsComp: hands);
- return;
- }
-
- // This is shit code until hands get fixed and give an easy way to enumerate over items, starting with the currently active item.
- foreach (var held in Hands.EnumerateHeld((user, hands)))
- {
- TryMergeStacks(item, held, out _, donorStack: itemStack);
-
- if (itemStack.Count == 0)
- return;
- }
-
- Hands.PickupOrDrop(user, item, handsComp: hands);
- }
-
- public virtual void SetCount(EntityUid uid, int amount, StackComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- // Do nothing if amount is already the same.
- if (amount == component.Count)
- return;
-
- // Store old value for event-raising purposes...
- var old = component.Count;
-
- // Clamp the value.
- amount = Math.Min(amount, GetMaxCount(component));
- amount = Math.Max(amount, 0);
+ SubscribeLocalEvent<StackComponent, InteractUsingEvent>(OnStackInteractUsing);
+ SubscribeLocalEvent<StackComponent, ComponentGetState>(OnStackGetState);
+ SubscribeLocalEvent<StackComponent, ComponentHandleState>(OnStackHandleState);
+ SubscribeLocalEvent<StackComponent, ComponentStartup>(OnStackStarted);
+ SubscribeLocalEvent<StackComponent, ExaminedEvent>(OnStackExamined);
- // Server-side override deletes the entity if count == 0
- component.Count = amount;
- Dirty(uid, component);
+ SubscribeLocalEvent<StackComponent, BeforeIngestedEvent>(OnBeforeEaten);
+ SubscribeLocalEvent<StackComponent, IngestedEvent>(OnEaten);
+ SubscribeLocalEvent<StackComponent, GetVerbsEvent<AlternativeVerb>>(OnStackAlternativeInteract);
- Appearance.SetData(uid, StackVisuals.Actual, component.Count);
- RaiseLocalEvent(uid, new StackCountChangedEvent(old, component.Count));
- }
-
- /// <summary>
- /// Try to use an amount of items on this stack. Returns whether this succeeded.
- /// </summary>
- public bool Use(EntityUid uid, int amount, StackComponent? stack = null)
- {
- if (!Resolve(uid, ref stack))
- return false;
+ _vvm.GetTypeHandler<StackComponent>()
+ .AddPath(nameof(StackComponent.Count), (_, comp) => comp.Count, SetCount);
+ }
- // Check if we have enough things in the stack for this...
- if (stack.Count < amount)
- {
- // Not enough things in the stack, return false.
- return false;
- }
+ public override void Shutdown()
+ {
+ base.Shutdown();
- // We do have enough things in the stack, so remove them and change.
- if (!stack.Unlimited)
- {
- SetCount(uid, stack.Count - amount, stack);
- }
+ _vvm.GetTypeHandler<StackComponent>()
+ .RemovePath(nameof(StackComponent.Count));
+ }
- return true;
- }
+ private void OnStackInteractUsing(Entity<StackComponent> ent, ref InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
- /// <summary>
- /// Tries to merge a stack into any of the stacks it is touching.
- /// </summary>
- /// <returns>Whether or not it was successfully merged into another stack</returns>
- public bool TryMergeToContacts(EntityUid uid, StackComponent? stack = null, TransformComponent? xform = null)
- {
- if (!Resolve(uid, ref stack, ref xform, false))
- return false;
+ if (!TryComp<StackComponent>(args.Used, out var recipientStack))
+ return;
- var map = xform.MapID;
- var bounds = _physics.GetWorldAABB(uid);
- var intersecting = new HashSet<Entity<StackComponent>>();
- _entityLookup.GetEntitiesIntersecting(map, bounds, intersecting, LookupFlags.Dynamic | LookupFlags.Sundries);
+ // Transfer stacks from ground to hand
+ if (!TryMergeStacks((ent.Owner, ent.Comp), (args.Used, recipientStack), out var transferred))
+ return; // if nothing transferred, leave without a pop-up
- var merged = false;
- foreach (var otherStack in intersecting)
- {
- var otherEnt = otherStack.Owner;
- // if you merge a ton of stacks together, you will end up deleting a few by accident.
- if (TerminatingOrDeleted(otherEnt) || EntityManager.IsQueuedForDeletion(otherEnt))
- continue;
-
- if (!TryMergeStacks(uid, otherEnt, out _, stack, otherStack))
- continue;
- merged = true;
-
- if (stack.Count <= 0)
- break;
- }
- return merged;
- }
+ args.Handled = true;
- /// <summary>
- /// Gets the amount of items in a stack. If it cannot be stacked, returns 1.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="component"></param>
- /// <returns></returns>
- public int GetCount(EntityUid uid, StackComponent? component = null)
- {
- return Resolve(uid, ref component, false) ? component.Count : 1;
- }
+ // interaction is done, the rest is just generating a pop-up
- /// <summary>
- /// Gets the max count for a given entity prototype
- /// </summary>
- /// <param name="entityId"></param>
- /// <returns></returns>
- [PublicAPI]
- public int GetMaxCount(string entityId)
- {
- var entProto = _prototype.Index<EntityPrototype>(entityId);
- entProto.TryGetComponent<StackComponent>(out var stackComp, EntityManager.ComponentFactory);
- return GetMaxCount(stackComp);
- }
+ var popupPos = args.ClickLocation;
+ var userCoords = Transform(args.User).Coordinates;
- /// <summary>
- /// Gets the max count for a given entity
- /// </summary>
- /// <param name="uid"></param>
- /// <returns></returns>
- [PublicAPI]
- public int GetMaxCount(EntityUid uid)
+ if (!popupPos.IsValid(EntityManager))
{
- return GetMaxCount(CompOrNull<StackComponent>(uid));
+ popupPos = userCoords;
}
- /// <summary>
- /// Gets the maximum amount that can be fit on a stack.
- /// </summary>
- /// <remarks>
- /// <p>
- /// if there's no stackcomp, this equals 1. Otherwise, if there's a max
- /// count override, it equals that. It then checks for a max count value
- /// on the prototype. If there isn't one, it defaults to the max integer
- /// value (unlimimted).
- /// </p>
- /// </remarks>
- /// <param name="component"></param>
- /// <returns></returns>
- public int GetMaxCount(StackComponent? component)
+ switch (transferred)
{
- if (component == null)
- return 1;
-
- if (component.MaxCountOverride != null)
- return component.MaxCountOverride.Value;
+ case > 0:
+ Popup.PopupClient($"+{transferred}", popupPos, args.User);
- if (string.IsNullOrEmpty(component.StackTypeId))
- return 1;
-
- var stackProto = _prototype.Index<StackPrototype>(component.StackTypeId);
+ if (GetAvailableSpace(recipientStack) == 0)
+ {
+ Popup.PopupClient(Loc.GetString("comp-stack-becomes-full"),
+ popupPos.Offset(new Vector2(0, -0.5f)),
+ args.User);
+ }
- return stackProto.MaxCount ?? int.MaxValue;
- }
+ break;
- /// <summary>
- /// Gets the remaining space in a stack.
- /// </summary>
- /// <param name="component"></param>
- /// <returns></returns>
- [PublicAPI]
- public int GetAvailableSpace(StackComponent component)
- {
- return GetMaxCount(component) - component.Count;
+ case 0 when GetAvailableSpace(recipientStack) == 0:
+ Popup.PopupClient(Loc.GetString("comp-stack-already-full"), popupPos, args.User);
+ break;
}
- /// <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);
- }
+ var localRotation = Transform(args.Used).LocalRotation;
+ _storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
+ }
- /// <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;
+ private void OnStackStarted(Entity<StackComponent> ent, ref ComponentStartup args)
+ {
+ if (!TryComp(ent.Owner, out AppearanceComponent? appearance))
+ return;
- if (insertStack.StackTypeId != targetStack.StackTypeId)
- return false;
+ Appearance.SetData(ent.Owner, StackVisuals.Actual, ent.Comp.Count, appearance);
+ Appearance.SetData(ent.Owner, StackVisuals.MaxCount, GetMaxCount(ent.Comp), appearance);
+ Appearance.SetData(ent.Owner, StackVisuals.Hide, false, appearance);
+ }
- var available = GetAvailableSpace(targetStack);
+ private void OnStackGetState(Entity<StackComponent> ent, ref ComponentGetState args)
+ {
+ args.State = new StackComponentState(ent.Comp.Count, ent.Comp.MaxCountOverride, ent.Comp.Unlimited);
+ }
- if (available <= 0)
- return false;
+ private void OnStackHandleState(Entity<StackComponent> ent, ref ComponentHandleState args)
+ {
+ if (args.Current is not StackComponentState cast)
+ return;
- var change = Math.Min(available, count);
+ ent.Comp.MaxCountOverride = cast.MaxCountOverride;
+ ent.Comp.Unlimited = cast.Unlimited;
+ // This will change the count and call events.
+ SetCount(ent.AsNullable(), cast.Count);
+ }
- SetCount(targetEnt, targetStack.Count + change, targetStack);
- SetCount(insertEnt, insertStack.Count - change, insertStack);
- return true;
- }
+ private void OnStackExamined(Entity<StackComponent> ent, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ args.PushMarkup(
+ Loc.GetString("comp-stack-examine-detail-count",
+ ("count", ent.Comp.Count),
+ ("markupCountColor", "lightgray")
+ )
+ );
+ }
- private void OnStackStarted(EntityUid uid, StackComponent component, ComponentStartup args)
- {
- if (!TryComp(uid, out AppearanceComponent? appearance))
- return;
+ private void OnBeforeEaten(Entity<StackComponent> eaten, ref BeforeIngestedEvent args)
+ {
+ if (args.Cancelled)
+ return;
- Appearance.SetData(uid, StackVisuals.Actual, component.Count, appearance);
- Appearance.SetData(uid, StackVisuals.MaxCount, GetMaxCount(component), appearance);
- Appearance.SetData(uid, StackVisuals.Hide, false, appearance);
- }
+ if (args.Solution is not { } sol)
+ return;
- private void OnStackGetState(EntityUid uid, StackComponent component, ref ComponentGetState args)
+ // If the entity is empty and is a lingering entity we can't eat from it.
+ if (eaten.Comp.Count <= 0)
{
- args.State = new StackComponentState(component.Count, component.MaxCountOverride);
+ args.Cancelled = true;
+ return;
}
- private void OnStackHandleState(EntityUid uid, StackComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not StackComponentState cast)
- return;
+ /*
+ Edible stacked items is near completely evil so we must choose one of the following:
+ - Option 1: Eat the entire solution each bite and reduce the stack by 1.
+ - Option 2: Multiply the solution eaten by the stack size.
+ - Option 3: Divide the solution consumed by stack size.
+ The easiest and safest option is and always will be Option 1 otherwise we risk reagent deletion or duplication.
+ That is why we cancel if we cannot set the minimum to the entire volume of the solution.
+ */
+ if (args.TryNewMinimum(sol.Volume))
+ return;
+
+ args.Cancelled = true;
+ }
- component.MaxCountOverride = cast.MaxCount;
- // This will change the count and call events.
- SetCount(uid, cast.Count, component);
- }
+ private void OnEaten(Entity<StackComponent> eaten, ref IngestedEvent args)
+ {
+ if (!TryUse(eaten.AsNullable(), 1))
+ return;
- private void OnStackExamined(EntityUid uid, StackComponent component, ExaminedEvent args)
+ // We haven't eaten the whole stack yet or are unable to eat it completely.
+ if (eaten.Comp.Count > 0)
{
- if (!args.IsInDetailsRange)
- return;
-
- args.PushMarkup(
- Loc.GetString("comp-stack-examine-detail-count",
- ("count", component.Count),
- ("markupCountColor", "lightgray")
- )
- );
+ args.Refresh = true;
+ return;
}
- private void OnBeforeEaten(Entity<StackComponent> eaten, ref BeforeIngestedEvent args)
- {
- if (args.Cancelled)
- return;
-
- if (args.Solution is not { } sol)
- return;
+ // Here to tell the food system to do destroy stuff.
+ args.Destroy = true;
+ }
- // If the entity is empty and is a lingering entity we can't eat from it.
- if (eaten.Comp.Count <= 0)
- {
- args.Cancelled = true;
- return;
- }
-
- /*
- Edible stacked items is near completely evil so we must choose one of the following:
- - Option 1: Eat the entire solution each bite and reduce the stack by 1.
- - Option 2: Multiply the solution eaten by the stack size.
- - Option 3: Divide the solution consumed by stack size.
- The easiest and safest option is and always will be Option 1 otherwise we risk reagent deletion or duplication.
- That is why we cancel if we cannot set the minimum to the entire volume of the solution.
- */
- if(args.TryNewMinimum(sol.Volume))
- return;
+ private void OnStackAlternativeInteract(Entity<StackComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Count == 1)
+ return;
- args.Cancelled = true;
- }
+ var user = args.User; // Can't pass ref events into verbs
- private void OnEaten(Entity<StackComponent> eaten, ref IngestedEvent args)
+ AlternativeVerb halve = new()
{
- if (!Use(eaten, 1))
- return;
-
- // We haven't eaten the whole stack yet or are unable to eat it completely.
- if (eaten.Comp.Count > 0)
- {
- args.Refresh = true;
- return;
- }
-
- // Here to tell the food system to do destroy stuff.
- args.Destroy = true;
- }
-
- private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args)
+ Text = Loc.GetString("comp-stack-split-halve"),
+ Category = VerbCategory.Split,
+ Act = () => UserSplit(ent, user, ent.Comp.Count / 2),
+ Priority = 1
+ };
+ args.Verbs.Add(halve);
+
+ var priority = 0;
+ foreach (var amount in DefaultSplitAmounts)
{
- if (!args.CanAccess || !args.CanInteract || args.Hands == null || stack.Count == 1)
- return;
+ if (amount >= ent.Comp.Count)
+ continue;
- AlternativeVerb halve = new()
+ AlternativeVerb verb = new()
{
- Text = Loc.GetString("comp-stack-split-halve"),
+ Text = amount.ToString(),
Category = VerbCategory.Split,
- Act = () => UserSplit(uid, args.User, stack.Count / 2, stack),
- Priority = 1
+ Act = () => UserSplit(ent, user, amount),
+ // we want to sort by size, not alphabetically by the verb text.
+ Priority = priority
};
- args.Verbs.Add(halve);
- var priority = 0;
- foreach (var amount in DefaultSplitAmounts)
- {
- if (amount >= stack.Count)
- continue;
+ priority--;
- AlternativeVerb verb = new()
- {
- Text = amount.ToString(),
- Category = VerbCategory.Split,
- Act = () => UserSplit(uid, args.User, amount, stack),
- // we want to sort by size, not alphabetically by the verb text.
- Priority = priority
- };
-
- priority--;
-
- args.Verbs.Add(verb);
- }
+ args.Verbs.Add(verb);
}
+ }
- /// <remarks>
- /// OnStackAlternativeInteract() was moved to shared in order to faciliate prediction of stack splitting verbs.
- /// However, prediction of interacitons with spawned entities is non-functional (or so i'm told)
- /// So, UserSplit() and Split() should remain on the server for the time being.
- /// This empty virtual method allows for UserSplit() to be called on the server from the client.
- /// When prediction is improved, those two methods should be moved to shared, in order to predict the splitting itself (not just the verbs)
- /// </remarks>
- protected virtual void UserSplit(EntityUid uid, EntityUid userUid, int amount,
- StackComponent? stack = null,
- TransformComponent? userTransform = null)
- {
+ /// <remarks>
+ /// OnStackAlternativeInteract() was moved to shared in order to faciliate prediction of stack splitting verbs.
+ /// However, prediction of interacitons with spawned entities is non-functional (or so i'm told)
+ /// So, UserSplit() and Split() should remain on the server for the time being.
+ /// This empty virtual method allows for UserSplit() to be called on the server from the client.
+ /// When prediction is improved, those two methods should be moved to shared, in order to predict the splitting itself (not just the verbs)
+ /// </remarks>
+ protected virtual void UserSplit(Entity<StackComponent> stack, Entity<TransformComponent?> user, int amount)
+ {
- }
}
+}
+/// <summary>
+/// Event raised when a stack's count has changed.
+/// </summary>
+public sealed class StackCountChangedEvent : EntityEventArgs
+{
/// <summary>
- /// Event raised when a stack's count has changed.
+ /// The old stack count.
/// </summary>
- public sealed class StackCountChangedEvent : EntityEventArgs
- {
- /// <summary>
- /// The old stack count.
- /// </summary>
- public int OldCount;
+ public int OldCount;
- /// <summary>
- /// The new stack count.
- /// </summary>
- public int NewCount;
+ /// <summary>
+ /// The new stack count.
+ /// </summary>
+ public int NewCount;
- public StackCountChangedEvent(int oldCount, int newCount)
- {
- OldCount = oldCount;
- NewCount = newCount;
- }
+ public StackCountChangedEvent(int oldCount, int newCount)
+ {
+ OldCount = oldCount;
+ NewCount = newCount;
}
}
using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-namespace Content.Shared.Stacks
+namespace Content.Shared.Stacks;
+
+/// <summary>
+/// Component on an entity that represents a stack of identical things, usually materials.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedStackSystem))]
+public sealed partial class StackComponent : Component
{
- [RegisterComponent, NetworkedComponent]
- public sealed partial class StackComponent : Component
- {
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("stackType", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<StackPrototype>))]
- public string StackTypeId { get; private set; } = default!;
+ /// <summary>
+ /// What stack type we are.
+ /// </summary>
+ [DataField("stackType", required: true)]
+ public ProtoId<StackPrototype> StackTypeId = default!;
- /// <summary>
- /// Current stack count.
- /// Do NOT set this directly, use the <see cref="SharedStackSystem.SetCount"/> method instead.
- /// </summary>
- [DataField("count")]
- public int Count { get; set; } = 30;
+ /// <summary>
+ /// Current stack count.
+ /// Do NOT set this directly, use the <see cref="SharedStackSystem.SetCount"/> method instead.
+ /// </summary>
+ [DataField]
+ public int Count = 30;
- /// <summary>
- /// Max amount of things that can be in the stack.
- /// Overrides the max defined on the stack prototype.
- /// </summary>
- [ViewVariables(VVAccess.ReadOnly)]
- [DataField("maxCountOverride")]
- public int? MaxCountOverride { get; set; }
+ /// <summary>
+ /// Max amount of things that can be in the stack.
+ /// Overrides the max defined on the stack prototype.
+ /// </summary>
+ [DataField]
+ public int? MaxCountOverride;
- /// <summary>
- /// Set to true to not reduce the count when used.
- /// Note that <see cref="Count"/> still limits the amount that can be used at any one time.
- /// </summary>
- [DataField("unlimited")]
- [ViewVariables(VVAccess.ReadOnly)]
- public bool Unlimited { get; set; }
+ /// <summary>
+ /// Set to true to not reduce the count when used.
+ /// </summary>
+ [DataField]
+ public bool Unlimited;
- [DataField("throwIndividually"), ViewVariables(VVAccess.ReadWrite)]
- public bool ThrowIndividually { get; set; } = false;
+ /// <summary>
+ /// When throwing this item, do we want to only throw one part of the stack or the whole stack at once?
+ /// </summary>
+ [DataField]
+ public bool ThrowIndividually;
- [ViewVariables]
- public bool UiUpdateNeeded { get; set; }
+ /// <summary>
+ /// Used by StackStatusControl in client to update UI.
+ /// </summary>
+ [ViewVariables]
+ [Access(typeof(SharedStackSystem), Other = AccessPermissions.ReadWrite)] // Set by StackStatusControl
+ public bool UiUpdateNeeded { get; set; }
- /// <summary>
- /// Default IconLayer stack.
- /// </summary>
- [DataField("baseLayer")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string BaseLayer = "";
+ /// <summary>
+ /// Default IconLayer stack.
+ /// </summary>
+ [DataField]
+ public string BaseLayer = "";
- /// <summary>
- /// Determines if the visualizer uses composite or non-composite layers for icons. Defaults to false.
- ///
- /// <list type="bullet">
- /// <item>
- /// <description>false: they are opaque and mutually exclusive (e.g. sprites in a cable coil). <b>Default value</b></description>
- /// </item>
- /// <item>
- /// <description>true: they are transparent and thus layered one over another in ascending order first</description>
- /// </item>
- /// </list>
- ///
- /// </summary>
- [DataField("composite")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool IsComposite;
+ /// <summary>
+ /// Determines if the visualizer uses composite or non-composite layers for icons. Defaults to false.
+ ///
+ /// <list type="bullet">
+ /// <item>
+ /// <description>false: they are opaque and mutually exclusive (e.g. sprites in a cable coil). <b>Default value</b></description>
+ /// </item>
+ /// <item>
+ /// <description>true: they are transparent and thus layered one over another in ascending order first</description>
+ /// </item>
+ /// </list>
+ ///
+ /// </summary>
+ [DataField("composite")]
+ public bool IsComposite;
- /// <summary>
- /// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
- /// e.g. <code>_spriteLayers[0]</code> is lower stack level than <code>_spriteLayers[1]</code>.
- /// </summary>
- [DataField("layerStates")]
- [ViewVariables(VVAccess.ReadWrite)]
- public List<string> LayerStates = new();
+ /// <summary>
+ /// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
+ /// e.g. <code>_spriteLayers[0]</code> is lower stack level than <code>_spriteLayers[1]</code>.
+ /// </summary>
+ [DataField]
+ public List<string> LayerStates = new();
- /// <summary>
- /// An optional function to convert the amounts used to adjust a stack's appearance.
- /// Useful for different denominations of cash, for example.
- /// </summary>
- [DataField]
- public StackLayerFunction LayerFunction = StackLayerFunction.None;
- }
+ /// <summary>
+ /// An optional function to convert the amounts used to adjust a stack's appearance.
+ /// Useful for different denominations of cash, for example.
+ /// </summary>
+ [DataField]
+ public StackLayerFunction LayerFunction = StackLayerFunction.None;
+}
- [Serializable, NetSerializable]
- public sealed class StackComponentState : ComponentState
- {
- public int Count { get; }
- public int? MaxCount { get; }
+[Serializable, NetSerializable]
+public sealed class StackComponentState : ComponentState
+{
+ public int Count { get; }
+ public int? MaxCountOverride { get; }
+ public bool Unlimited { get; }
- public StackComponentState(int count, int? maxCount)
- {
- Count = count;
- MaxCount = maxCount;
- }
+ public StackComponentState(int count, int? maxCountOverride, bool unlimited)
+ {
+ Count = count;
+ MaxCountOverride = maxCountOverride;
+ Unlimited = unlimited;
}
+}
- [Serializable, NetSerializable]
- public enum StackLayerFunction : byte
- {
- // <summary>
- // No operation performed.
- // </summary>
- None,
+[Serializable, NetSerializable]
+public enum StackLayerFunction : byte
+{
+ // <summary>
+ // No operation performed.
+ // </summary>
+ None,
- // <summary>
- // Arbitrarily thresholds the stack amount for each layer.
- // Expects entity to have StackLayerThresholdComponent.
- // </summary>
- Threshold
- }
+ // <summary>
+ // Arbitrarily thresholds the stack amount for each layer.
+ // Expects entity to have StackLayerThresholdComponent.
+ // </summary>
+ Threshold
}
namespace Content.Shared.Stacks;
+/// <summary>
+/// Prototype used to combine and spawn like-entities for <see cref="SharedStackSystem"/>.
+/// </summary>
[Prototype]
public sealed partial class StackPrototype : IPrototype, IInheritingPrototype
{
public bool Abstract { get; private set; }
/// <summary>
- /// Human-readable name for this stack type e.g. "Steel"
+ /// Human-readable name for this stack type e.g. "Steel"
/// </summary>
/// <remarks>This is a localization string ID.</remarks>
[DataField]
public LocId Name { get; private set; } = string.Empty;
/// <summary>
- /// An icon that will be used to represent this stack type.
+ /// An icon that will be used to represent this stack type.
/// </summary>
[DataField]
public SpriteSpecifier? Icon { get; private set; }
/// <summary>
- /// The entity id that will be spawned by default from this stack.
+ /// The entity id that will be spawned by default from this stack.
/// </summary>
[DataField(required: true)]
- public EntProtoId Spawn { get; private set; } = string.Empty;
+ public EntProtoId<StackComponent> Spawn { get; private set; } = string.Empty;
/// <summary>
- /// The maximum amount of things that can be in a stack.
- /// Can be overriden on <see cref="StackComponent"/>
- /// if null, simply has unlimited max count.
+ /// The maximum amount of things that can be in a stack, can be overriden on <see cref="StackComponent"/>.
+ /// If null, simply has unlimited max count.
/// </summary>
[DataField]
public int? MaxCount { get; private set; }
Actual,
/// <summary>
/// The total amount of elements in the stack. If unspecified, the visualizer assumes
- /// its
+ /// it's StackComponent.LayerStates.Count
/// </summary>
MaxCount,
Hide
if (!_stackQuery.TryGetComponent(ent, out var containedStack))
continue;
- if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack))
+ if (!_stack.TryMergeStacks((insertEnt, insertStack), (ent, containedStack), out var _))
continue;
stackedEntity = ent;
return GetCumulativeItemAreas(uid) < uid.Comp.Grid.GetArea() || HasSpaceInStacks(uid);
}
- private bool HasSpaceInStacks(Entity<StorageComponent?> uid, string? stackType = null)
+ private bool HasSpaceInStacks(Entity<StorageComponent?> uid, ProtoId<StackPrototype>? stackType = null)
{
if (!Resolve(uid, ref uid.Comp))
return false;
if (!_stackQuery.TryGetComponent(contained, out var stack))
continue;
- if (stackType != null && !stack.StackTypeId.Equals(stackType))
+ if (stackType != null && stack.StackTypeId != stackType)
continue;
if (_stack.GetAvailableSpace(stack) == 0)
/// doesn't necessarily refer to the full name of the currency, only
/// that which is displayed to the user.
/// </summary>
- [DataField("displayName")]
+ [DataField]
public string DisplayName { get; private set; } = string.Empty;
/// <summary>
/// The physical entity of the currency
/// </summary>
- [DataField("cash", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<FixedPoint2, EntityPrototype>))]
- public Dictionary<FixedPoint2, string>? Cash { get; private set; }
+ [DataField]
+ public Dictionary<FixedPoint2, EntProtoId>? Cash { get; private set; }
/// <summary>
/// Whether or not this currency can be withdrawn from a shop by a player. Requires a valid entityId.
/// </summary>
- [DataField("canWithdraw")]
+ [DataField]
public bool CanWithdraw { get; private set; } = true;
}
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
{
- if (!_stackSystem.Use(uid, 1, stack))
+ if (!_stackSystem.TryUse((uid, stack), 1))
continue;
PlaceAt(args.User, gridUid, mapGrid, location, currentTileDefinition.TileId, component.PlaceTileSound);
}
else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
{
- if (!_stackSystem.Use(uid, 1, stack))
+ if (!_stackSystem.TryUse((uid, stack), 1))
continue;
args.Handled = true;