Stack System Cleanup (#38872)
* eye on the prize * OnStackInteractUsing, TryMergeStacks, TryMergeToHands, TryMergeToContacts * namespace * Use, get count, getMaxCount * component access * add regions, mark TODO * obsolete TryAdd, public TryMergeStacks * GetMaxCount * event handlers * event handlers * SetCount * client server event handlers * move to shared * Revert "move to shared" This reverts commit 45540a2d6b8e1e6d2a8f83a584267776c7edcd73. * misc changes to shared * split * spawn and SpawnNextToOrDrop * SpawnMultipleAtPosition, SpawnMultipleNextToOrDrop, CalculateSpawns, general server cleanup * Rename Use to TryUse. * Small misc changes * Remove obsolete functions * Remove some SetCount calls * Partialize * small misc change * don't nuke the git dif with the namespace block * Comments and reordering * touchup to UpdateLingering * Summary comment for StackStatusControl * Last pass * Actual last pass (for now) * I know myself too well * fixup * goodbye lingering * fixes * review * fix test * second look * fix test * forgot * remove early comp getting --------- Co-authored-by: iaada <iaada@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,9 @@ using Robust.Shared.Timing;
|
|||||||
|
|
||||||
namespace Content.Client.Stack;
|
namespace Content.Client.Stack;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by hands in player UI to display the stack count.
|
||||||
|
/// </summary>
|
||||||
public sealed class StackStatusControl : Control
|
public sealed class StackStatusControl : Control
|
||||||
{
|
{
|
||||||
private readonly StackComponent _parent;
|
private readonly StackComponent _parent;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Client.Items;
|
using Content.Client.Items;
|
||||||
using Content.Client.Storage.Systems;
|
using Content.Client.Storage.Systems;
|
||||||
using Content.Shared.Stacks;
|
using Content.Shared.Stacks;
|
||||||
@@ -7,6 +6,7 @@ using Robust.Client.GameObjects;
|
|||||||
|
|
||||||
namespace Content.Client.Stack
|
namespace Content.Client.Stack
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class StackSystem : SharedStackSystem
|
public sealed class StackSystem : SharedStackSystem
|
||||||
{
|
{
|
||||||
@@ -16,33 +16,21 @@ namespace Content.Client.Stack
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<StackComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
SubscribeLocalEvent<StackComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||||
Subs.ItemStatus<StackComponent>(ent => new StackStatusControl(ent));
|
Subs.ItemStatus<StackComponent>(ent => new StackStatusControl(ent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetCount(EntityUid uid, int amount, StackComponent? component = null)
|
#region Appearance
|
||||||
|
|
||||||
|
private void OnAppearanceChange(Entity<StackComponent> ent, ref AppearanceChangeEvent args)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
var (uid, comp) = ent;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAppearanceChange(EntityUid uid, StackComponent comp, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
if (args.Sprite == null || comp.LayerStates.Count < 1)
|
if (args.Sprite == null || comp.LayerStates.Count < 1)
|
||||||
return;
|
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))
|
if (!_appearanceSystem.TryGetData<int>(uid, StackVisuals.Actual, out var actual, args.Component))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -56,9 +44,24 @@ namespace Content.Client.Stack
|
|||||||
ApplyLayerFunction((uid, comp), ref actual, ref maxCount);
|
ApplyLayerFunction((uid, comp), ref actual, ref maxCount);
|
||||||
|
|
||||||
if (comp.IsComposite)
|
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
|
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>
|
/// <summary>
|
||||||
@@ -67,7 +70,7 @@ namespace Content.Client.Stack
|
|||||||
/// <param name="ent">The entity considered.</param>
|
/// <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="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>
|
/// <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)
|
private bool ApplyLayerFunction(Entity<StackComponent> ent, ref int actual, ref int maxCount)
|
||||||
{
|
{
|
||||||
switch (ent.Comp.LayerFunction)
|
switch (ent.Comp.LayerFunction)
|
||||||
@@ -78,8 +81,10 @@ namespace Content.Client.Stack
|
|||||||
ApplyThreshold(threshold, ref actual, ref maxCount);
|
ApplyThreshold(threshold, ref actual, ref maxCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No function applied.
|
// No function applied.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -105,7 +110,10 @@ namespace Content.Client.Stack
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
actual = newActual;
|
actual = newActual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,13 +215,10 @@ public sealed class CargoTest
|
|||||||
|
|
||||||
[TestPrototypes]
|
[TestPrototypes]
|
||||||
private const string StackProto = @"
|
private const string StackProto = @"
|
||||||
- type: entity
|
|
||||||
id: A
|
|
||||||
|
|
||||||
- type: stack
|
- type: stack
|
||||||
id: StackProto
|
id: StackProto
|
||||||
name: stack-steel
|
name: stack-steel
|
||||||
spawn: A
|
spawn: StackEnt
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: StackEnt
|
id: StackEnt
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ public sealed class CraftingTests : InteractionTest
|
|||||||
Assert.That(sys.IsEntityInContainer(shard), Is.True);
|
Assert.That(sys.IsEntityInContainer(shard), Is.True);
|
||||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||||
Assert.That(rodStack, Has.Count.EqualTo(8));
|
Assert.That(rodStack.Count, Is.EqualTo(8));
|
||||||
Assert.That(wireStack, Has.Count.EqualTo(7));
|
Assert.That(wireStack.Count, Is.EqualTo(7));
|
||||||
|
|
||||||
await FindEntity(Spear, shouldSucceed: false);
|
await FindEntity(Spear, shouldSucceed: false);
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public abstract partial class InteractionTest
|
|||||||
await Server.WaitPost(() =>
|
await Server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
uid = SEntMan.SpawnEntity(stackProto.Spawn, coords);
|
uid = SEntMan.SpawnEntity(stackProto.Spawn, coords);
|
||||||
Stack.SetCount(uid, spec.Quantity);
|
Stack.SetCount((uid, null), spec.Quantity);
|
||||||
});
|
});
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -469,7 +469,8 @@ public sealed class MaterialArbitrageTest
|
|||||||
await server.WaitPost(() =>
|
await server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
var ent = entManager.SpawnEntity(id, testMap.GridCoords);
|
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);
|
priceCache[id] = price = pricing.GetPrice(ent, false);
|
||||||
entManager.DeleteEntity(ent);
|
entManager.DeleteEntity(ent);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Content.IntegrationTests.Tests.Materials
|
|||||||
$"{proto.ID} material has no stack prototype");
|
$"{proto.ID} material has no stack prototype");
|
||||||
|
|
||||||
if (stackProto != null)
|
if (stackProto != null)
|
||||||
Assert.That(proto.StackEntity, Is.EqualTo(stackProto.Spawn));
|
Assert.That(proto.StackEntity, Is.EqualTo(stackProto.Spawn.Id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
// Unbounded intentionally.
|
// 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) =>
|
_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,
|
Impact = LogImpact.Medium,
|
||||||
@@ -429,7 +429,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill-stack.png")),
|
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill-stack.png")),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
_stackSystem.SetCount(args.Target, _stackSystem.GetMaxCount(stack), stack);
|
_stackSystem.SetCount((args.Target, stack), _stackSystem.GetMaxCount(stack));
|
||||||
},
|
},
|
||||||
Impact = LogImpact.Medium,
|
Impact = LogImpact.Medium,
|
||||||
Message = Loc.GetString("admin-trick-fill-stack-description"),
|
Message = Loc.GetString("admin-trick-fill-stack-description"),
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public sealed partial class CargoSystem
|
|||||||
if (args.Account == null)
|
if (args.Account == null)
|
||||||
{
|
{
|
||||||
var stackPrototype = _protoMan.Index(ent.Comp.CashType);
|
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 (!_emag.CheckFlag(ent, EmagType.Interaction))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public sealed partial class CloningSystem
|
|||||||
{
|
{
|
||||||
// if the clone is a stack as well, adjust the count of the copy
|
// if the clone is a stack as well, adjust the count of the copy
|
||||||
if (TryComp<StackComponent>(args.CloneUid, out var cloneStackComp))
|
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)
|
private void OnCloneItemLabel(Entity<LabelComponent> ent, ref CloningItemEvent args)
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ public sealed partial class GivePrototype : IGraphAction
|
|||||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
||||||
{
|
{
|
||||||
var stackSystem = entityManager.EntitySysManager.GetEntitySystem<StackSystem>();
|
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))
|
if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var item in stacks)
|
foreach (var item in stacks)
|
||||||
{
|
{
|
||||||
stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp);
|
stackSystem.TryMergeToHands(item, (userUid.Value, handsComp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Server.Construction.Completions
|
|||||||
|
|
||||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||||
{
|
{
|
||||||
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(uid, Amount);
|
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount((uid, null), Amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Content.Server.Construction.Completions
|
|||||||
{
|
{
|
||||||
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
|
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
|
||||||
var stack = entityManager.GetComponent<StackComponent>(stackEnt);
|
var stack = entityManager.GetComponent<StackComponent>(stackEnt);
|
||||||
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(stackEnt, Amount, stack);
|
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount((stackEnt, stack), Amount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ namespace Content.Server.Construction
|
|||||||
|
|
||||||
// TODO allow taking from several stacks.
|
// TODO allow taking from several stacks.
|
||||||
// Also update crafting steps to check if it works.
|
// 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)
|
if (splitStack == null)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public sealed partial class ConstructionSystem
|
|||||||
|
|
||||||
foreach (var (stackType, amount) in machineBoard.StackRequirements)
|
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))
|
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"}");
|
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
|||||||
return true;
|
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)
|
if (splitStack == null)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
|||||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.EntityManager.ComponentFactory))
|
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.EntityManager.ComponentFactory))
|
||||||
{
|
{
|
||||||
var spawned = system.EntityManager.SpawnEntity(entityId, xform.Coordinates.Offset(system.Random.NextVector2(-Offset, Offset)));
|
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();
|
system.EntityManager.GetComponent<TransformComponent>(spawned).LocalRotation = system.Random.NextAngle();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
|||||||
var spawned = SpawnInContainer
|
var spawned = SpawnInContainer
|
||||||
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
|
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
|
||||||
: system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
: system.EntityManager.SpawnEntity(entityId, position.Offset(getRandomVector()));
|
||||||
system.StackSystem.SetCount(spawned, count);
|
system.StackSystem.SetCount((spawned, null), count);
|
||||||
|
|
||||||
TransferForensics(spawned, system, owner);
|
TransferForensics(spawned, system, owner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ namespace Content.Server.Engineering.EntitySystems
|
|||||||
if (component.Deleted || !IsTileClear())
|
if (component.Deleted || !IsTileClear())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (TryComp(uid, out StackComponent? stackComp)
|
if (TryComp<StackComponent>(uid, out var stackComp)
|
||||||
&& component.RemoveOnInteract && !_stackSystem.Use(uid, 1, stackComp))
|
&& component.RemoveOnInteract && !_stackSystem.TryUse((uid, stackComp), 1))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ namespace Content.Server.Hands.Systems
|
|||||||
|
|
||||||
if (TryComp(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
|
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})
|
if (splitStack is not {Valid: true})
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
// If an entity has a stack component, use the stacktype instead of prototype id
|
// If an entity has a stack component, use the stacktype instead of prototype id
|
||||||
if (TryComp<StackComponent>(item, out var stackComp))
|
if (TryComp<StackComponent>(item, out var stackComp))
|
||||||
{
|
{
|
||||||
itemID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
|
itemID = _prototype.Index(stackComp.StackTypeId).Spawn;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -265,7 +265,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
{
|
{
|
||||||
_container.Remove(item, component.Storage);
|
_container.Remove(item, component.Storage);
|
||||||
}
|
}
|
||||||
_stack.Use(item, 1, stackComp);
|
_stack.ReduceCount((item, stackComp), 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||||||
scaledSolution.ScaleSolution(fitsCount);
|
scaledSolution.ScaleSolution(fitsCount);
|
||||||
solution = scaledSolution;
|
solution = scaledSolution;
|
||||||
|
|
||||||
_stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel
|
_stackSystem.ReduceCount((item, stack), fitsCount); // Setting to 0 will QueueDel
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,13 +136,13 @@ namespace Content.Server.Light.EntitySystems
|
|||||||
component.StateExpiryTime = (float)component.RefuelMaterialTime.TotalSeconds;
|
component.StateExpiryTime = (float)component.RefuelMaterialTime.TotalSeconds;
|
||||||
|
|
||||||
_nameModifier.RefreshNameModifiers(uid);
|
_nameModifier.RefreshNameModifiers(uid);
|
||||||
_stackSystem.SetCount(args.Used, stack.Count - 1, stack);
|
_stackSystem.ReduceCount((args.Used, stack), 1);
|
||||||
UpdateVisualizer((uid, component));
|
UpdateVisualizer((uid, component));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
component.StateExpiryTime += (float)component.RefuelMaterialTime.TotalSeconds;
|
component.StateExpiryTime += (float)component.RefuelMaterialTime.TotalSeconds;
|
||||||
_stackSystem.SetCount(args.Used, stack.Count - 1, stack);
|
_stackSystem.ReduceCount((args.Used, stack), 1);
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var volumePerSheet = composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == msg.Material).Value;
|
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;
|
volume = sheetsToExtract * volumePerSheet;
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
|
|||||||
if (amountToSpawn == 0)
|
if (amountToSpawn == 0)
|
||||||
return new List<EntityUid>();
|
return new List<EntityUid>();
|
||||||
|
|
||||||
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
|
return _stackSystem.SpawnMultipleAtPosition(materialProto.StackEntity.Value, amountToSpawn, coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public sealed partial class CableSystem
|
|||||||
return;
|
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;
|
return;
|
||||||
|
|
||||||
var newCable = Spawn(component.CablePrototypeId, _map.GridTileToLocal(gridUid, grid, snapPos));
|
var newCable = Spawn(component.CablePrototypeId, _map.GridTileToLocal(gridUid, grid, snapPos));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Stacks;
|
using Content.Shared.Stacks;
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -16,140 +15,238 @@ namespace Content.Server.Stack
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
#region Spawning
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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;
|
return null;
|
||||||
|
|
||||||
// Try to remove the amount of things we want to split from the original stack...
|
// 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;
|
return null;
|
||||||
|
|
||||||
// Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked...
|
if (!_prototypeManager.Resolve(ent.Comp.StackTypeId, out var stackType))
|
||||||
var prototype = _prototypeManager.TryIndex<StackPrototype>(stack.StackTypeId, out var stackType)
|
return null;
|
||||||
? stackType.Spawn.ToString()
|
|
||||||
: Prototype(uid)?.ID;
|
|
||||||
|
|
||||||
// Set the output parameter in the event instance to the newly split stack.
|
// 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))
|
// There should always be a StackComponent
|
||||||
{
|
var stackComp = Comp<StackComponent>(newEntity);
|
||||||
// Set the split stack's count.
|
|
||||||
SetCount(entity, amount, stackComp);
|
SetCount((newEntity, stackComp), amount);
|
||||||
// Don't let people dupe unlimited stacks
|
stackComp.Unlimited = false; // Don't let people dupe unlimited stacks
|
||||||
stackComp.Unlimited = false;
|
Dirty(newEntity, stackComp);
|
||||||
|
|
||||||
|
var ev = new StackSplitEvent(newEntity);
|
||||||
|
RaiseLocalEvent(ent, ref ev);
|
||||||
|
|
||||||
|
return newEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ev = new StackSplitEvent(entity);
|
#region SpawnAtPosition
|
||||||
RaiseLocalEvent(uid, ref ev);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawns a stack of a certain stack type and sets its count. Won't set the stack over its max.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The amount to set the spawned stack to.</param>
|
||||||
|
[PublicAPI]
|
||||||
|
public EntityUid SpawnAtPosition(int count, StackPrototype prototype, EntityCoordinates spawnPosition)
|
||||||
|
{
|
||||||
|
var entity = SpawnAtPosition(prototype.Spawn, spawnPosition); // The real SpawnAtPosition
|
||||||
|
|
||||||
|
SetCount((entity, null), count);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc cref="SpawnAtPosition(int, StackPrototype, EntityCoordinates)"/>
|
||||||
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
|
[PublicAPI]
|
||||||
/// </summary>
|
public EntityUid SpawnAtPosition(int count, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
|
||||||
public EntityUid Spawn(int amount, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
|
|
||||||
{
|
{
|
||||||
var proto = _prototypeManager.Index(id);
|
var proto = _prototypeManager.Index(id);
|
||||||
return Spawn(amount, proto, spawnPosition);
|
return SpawnAtPosition(count, proto, spawnPosition);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
|
|
||||||
/// </summary>
|
|
||||||
public EntityUid Spawn(int amount, 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);
|
|
||||||
|
|
||||||
// And finally, set the correct amount!
|
|
||||||
SetCount(entity, amount, stack);
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Say you want to spawn 97 units of something that has a max stack count of 30.
|
/// 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.
|
/// This would spawn 3 stacks of 30 and 1 stack of 7.
|
||||||
/// </summary>
|
/// </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(
|
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();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
|
||||||
|
|
||||||
var spawnedEnts = new List<EntityUid>();
|
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);
|
spawnedEnts.Add(entity);
|
||||||
SetCount(entity, count);
|
if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
|
||||||
|
SetCount((entity, stackComp), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return spawnedEnts;
|
return spawnedEnts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="SpawnMultiple(string,int,EntityCoordinates)"/>
|
/// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
|
||||||
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityUid target)
|
[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(
|
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();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
|
||||||
|
|
||||||
var spawnedEnts = new List<EntityUid>();
|
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);
|
spawnedEnts.Add(entity);
|
||||||
SetCount(entity, count);
|
if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
|
||||||
|
SetCount((entity, stackComp), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return spawnedEnts;
|
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>
|
/// <summary>
|
||||||
/// Calculates how many stacks to spawn that total up to <paramref name="amount"/>.
|
/// Calculates how many stacks to spawn that total up to <paramref name="amount"/>.
|
||||||
/// </summary>
|
/// </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>
|
/// <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>();
|
var amounts = new List<int>();
|
||||||
while (amount > 0)
|
while (amount > 0)
|
||||||
{
|
{
|
||||||
@@ -161,28 +258,47 @@ namespace Content.Server.Stack
|
|||||||
return amounts;
|
return amounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UserSplit(EntityUid uid, EntityUid userUid, int amount,
|
/// <inheritdoc cref="CalculateSpawns(int, int)"/>
|
||||||
StackComponent? stack = null,
|
private List<int> CalculateSpawns(StackPrototype stackProto, int amount)
|
||||||
TransformComponent? userTransform = null)
|
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref stack))
|
return CalculateSpawns(GetMaxCount(stackProto), amount);
|
||||||
return;
|
}
|
||||||
|
|
||||||
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;
|
return;
|
||||||
|
|
||||||
if (amount <= 0)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split)
|
if (Split(stack.AsNullable(), amount, user.Comp.Coordinates) is not { } split)
|
||||||
return;
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ public sealed partial class StoreSystem
|
|||||||
{
|
{
|
||||||
var cashId = proto.Cash[value];
|
var cashId = proto.Cash[value];
|
||||||
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / 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)
|
if (ents.FirstOrDefault() is {} ent)
|
||||||
_hands.PickupOrDrop(buyer, ent);
|
_hands.PickupOrDrop(buyer, ent);
|
||||||
amountRemaining -= value * amountToSpawn;
|
amountRemaining -= value * amountToSpawn;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public sealed partial class StoreSystem : EntitySystem
|
|||||||
// same tick
|
// same tick
|
||||||
currency.Comp.Price.Clear();
|
currency.Comp.Price.Clear();
|
||||||
if (stack != null)
|
if (stack != null)
|
||||||
_stack.SetCount(currency.Owner, 0, stack);
|
_stack.SetCount((currency.Owner, stack), 0);
|
||||||
|
|
||||||
QueueDel(currency);
|
QueueDel(currency);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
|
|||||||
if (_whitelistSystem.IsWhitelistPass(crusher.CrushingWhitelist, contained))
|
if (_whitelistSystem.IsWhitelistPass(crusher.CrushingWhitelist, contained))
|
||||||
{
|
{
|
||||||
var amount = _random.Next(crusher.MinFragments, crusher.MaxFragments);
|
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)
|
foreach (var stack in stacks)
|
||||||
{
|
{
|
||||||
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
|
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ public sealed class HealingSystem : EntitySystem
|
|||||||
var dontRepeat = false;
|
var dontRepeat = false;
|
||||||
if (TryComp<StackComponent>(args.Used.Value, out var stackComp))
|
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;
|
dontRepeat = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public abstract partial class SharedFultonSystem : EntitySystem
|
|||||||
if (args.Cancelled || args.Target == null || !TryComp<FultonComponent>(args.Used, out var fulton))
|
if (args.Cancelled || args.Target == null || !TryComp<FultonComponent>(args.Used, out var fulton))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_stack.Use(args.Used.Value, 1))
|
if (!_stack.TryUse(args.Used.Value, 1))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
293
Content.Shared/Stacks/SharedStackSystem.API.cs
Normal file
293
Content.Shared/Stacks/SharedStackSystem.API.cs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Hands.Components;
|
|
||||||
using Content.Shared.Hands.EntitySystems;
|
using Content.Shared.Hands.EntitySystems;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition;
|
||||||
@@ -10,16 +9,18 @@ using Content.Shared.Verbs;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
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 IGameTiming _gameTiming = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly IViewVariablesManager _vvm = default!;
|
[Dependency] private readonly IViewVariablesManager _vvm = default!;
|
||||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||||
@@ -29,18 +30,21 @@ namespace Content.Shared.Stacks
|
|||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||||
[Dependency] private readonly SharedStorageSystem _storage = 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 static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StackComponent, InteractUsingEvent>(OnStackInteractUsing);
|
||||||
SubscribeLocalEvent<StackComponent, ComponentGetState>(OnStackGetState);
|
SubscribeLocalEvent<StackComponent, ComponentGetState>(OnStackGetState);
|
||||||
SubscribeLocalEvent<StackComponent, ComponentHandleState>(OnStackHandleState);
|
SubscribeLocalEvent<StackComponent, ComponentHandleState>(OnStackHandleState);
|
||||||
SubscribeLocalEvent<StackComponent, ComponentStartup>(OnStackStarted);
|
SubscribeLocalEvent<StackComponent, ComponentStartup>(OnStackStarted);
|
||||||
SubscribeLocalEvent<StackComponent, ExaminedEvent>(OnStackExamined);
|
SubscribeLocalEvent<StackComponent, ExaminedEvent>(OnStackExamined);
|
||||||
SubscribeLocalEvent<StackComponent, InteractUsingEvent>(OnStackInteractUsing);
|
|
||||||
SubscribeLocalEvent<StackComponent, BeforeIngestedEvent>(OnBeforeEaten);
|
SubscribeLocalEvent<StackComponent, BeforeIngestedEvent>(OnBeforeEaten);
|
||||||
SubscribeLocalEvent<StackComponent, IngestedEvent>(OnEaten);
|
SubscribeLocalEvent<StackComponent, IngestedEvent>(OnEaten);
|
||||||
SubscribeLocalEvent<StackComponent, GetVerbsEvent<AlternativeVerb>>(OnStackAlternativeInteract);
|
SubscribeLocalEvent<StackComponent, GetVerbsEvent<AlternativeVerb>>(OnStackAlternativeInteract);
|
||||||
@@ -57,26 +61,22 @@ namespace Content.Shared.Stacks
|
|||||||
.RemovePath(nameof(StackComponent.Count));
|
.RemovePath(nameof(StackComponent.Count));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStackInteractUsing(EntityUid uid, StackComponent stack, InteractUsingEvent args)
|
private void OnStackInteractUsing(Entity<StackComponent> ent, ref InteractUsingEvent args)
|
||||||
{
|
{
|
||||||
if (args.Handled)
|
if (args.Handled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryComp(args.Used, out StackComponent? recipientStack))
|
if (!TryComp<StackComponent>(args.Used, out var recipientStack))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var localRotation = Transform(args.Used).LocalRotation;
|
// Transfer stacks from ground to hand
|
||||||
|
if (!TryMergeStacks((ent.Owner, ent.Comp), (args.Used, recipientStack), out var transferred))
|
||||||
if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack))
|
return; // if nothing transferred, leave without a pop-up
|
||||||
return;
|
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
|
|
||||||
// interaction is done, the rest is just generating a pop-up
|
// interaction is done, the rest is just generating a pop-up
|
||||||
|
|
||||||
if (!_gameTiming.IsFirstTimePredicted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var popupPos = args.ClickLocation;
|
var popupPos = args.ClickLocation;
|
||||||
var userCoords = Transform(args.User).Coordinates;
|
var userCoords = Transform(args.User).Coordinates;
|
||||||
|
|
||||||
@@ -85,308 +85,63 @@ namespace Content.Shared.Stacks
|
|||||||
popupPos = userCoords;
|
popupPos = userCoords;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (transfered)
|
switch (transferred)
|
||||||
{
|
{
|
||||||
case > 0:
|
case > 0:
|
||||||
Popup.PopupCoordinates($"+{transfered}", popupPos, Filter.Local(), false);
|
Popup.PopupClient($"+{transferred}", popupPos, args.User);
|
||||||
|
|
||||||
if (GetAvailableSpace(recipientStack) == 0)
|
if (GetAvailableSpace(recipientStack) == 0)
|
||||||
{
|
{
|
||||||
Popup.PopupCoordinates(Loc.GetString("comp-stack-becomes-full"),
|
Popup.PopupClient(Loc.GetString("comp-stack-becomes-full"),
|
||||||
popupPos.Offset(new Vector2(0, -0.5f)), Filter.Local(), false);
|
popupPos.Offset(new Vector2(0, -0.5f)),
|
||||||
|
args.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0 when GetAvailableSpace(recipientStack) == 0:
|
case 0 when GetAvailableSpace(recipientStack) == 0:
|
||||||
Popup.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local(), false);
|
Popup.PopupClient(Loc.GetString("comp-stack-already-full"), popupPos, args.User);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localRotation = Transform(args.Used).LocalRotation;
|
||||||
_storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
|
_storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryMergeStacks(
|
private void OnStackStarted(Entity<StackComponent> ent, ref ComponentStartup args)
|
||||||
EntityUid donor,
|
|
||||||
EntityUid recipient,
|
|
||||||
out int transferred,
|
|
||||||
StackComponent? donorStack = null,
|
|
||||||
StackComponent? recipientStack = null)
|
|
||||||
{
|
{
|
||||||
transferred = 0;
|
if (!TryComp(ent.Owner, out AppearanceComponent? appearance))
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
return;
|
||||||
|
|
||||||
if (!Resolve(item, ref itemStack, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStackGetState(Entity<StackComponent> ent, ref ComponentGetState args)
|
||||||
{
|
{
|
||||||
// This isn't even a stack. Just try to pickup as normal.
|
args.State = new StackComponentState(ent.Comp.Count, ent.Comp.MaxCountOverride, ent.Comp.Unlimited);
|
||||||
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.
|
private void OnStackHandleState(Entity<StackComponent> ent, ref ComponentHandleState args)
|
||||||
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);
|
|
||||||
|
|
||||||
// Server-side override deletes the entity if count == 0
|
|
||||||
component.Count = amount;
|
|
||||||
Dirty(uid, component);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do have enough things in the stack, so remove them and change.
|
|
||||||
if (!stack.Unlimited)
|
|
||||||
{
|
|
||||||
SetCount(uid, stack.Count - amount, stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
var map = xform.MapID;
|
|
||||||
var bounds = _physics.GetWorldAABB(uid);
|
|
||||||
var intersecting = new HashSet<Entity<StackComponent>>();
|
|
||||||
_entityLookup.GetEntitiesIntersecting(map, bounds, intersecting, LookupFlags.Dynamic | LookupFlags.Sundries);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the max count for a given entity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[PublicAPI]
|
|
||||||
public int GetMaxCount(EntityUid uid)
|
|
||||||
{
|
|
||||||
return GetMaxCount(CompOrNull<StackComponent>(uid));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
if (component == null)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (component.MaxCountOverride != null)
|
|
||||||
return component.MaxCountOverride.Value;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(component.StackTypeId))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
var stackProto = _prototype.Index<StackPrototype>(component.StackTypeId);
|
|
||||||
|
|
||||||
return stackProto.MaxCount ?? int.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
if (insertStack.StackTypeId != targetStack.StackTypeId)
|
|
||||||
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))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Appearance.SetData(uid, StackVisuals.Actual, component.Count, appearance);
|
|
||||||
Appearance.SetData(uid, StackVisuals.MaxCount, GetMaxCount(component), appearance);
|
|
||||||
Appearance.SetData(uid, StackVisuals.Hide, false, appearance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStackGetState(EntityUid uid, StackComponent component, ref ComponentGetState args)
|
|
||||||
{
|
|
||||||
args.State = new StackComponentState(component.Count, component.MaxCountOverride);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStackHandleState(EntityUid uid, StackComponent component, ref ComponentHandleState args)
|
|
||||||
{
|
{
|
||||||
if (args.Current is not StackComponentState cast)
|
if (args.Current is not StackComponentState cast)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
component.MaxCountOverride = cast.MaxCount;
|
ent.Comp.MaxCountOverride = cast.MaxCountOverride;
|
||||||
|
ent.Comp.Unlimited = cast.Unlimited;
|
||||||
// This will change the count and call events.
|
// This will change the count and call events.
|
||||||
SetCount(uid, cast.Count, component);
|
SetCount(ent.AsNullable(), cast.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStackExamined(EntityUid uid, StackComponent component, ExaminedEvent args)
|
private void OnStackExamined(Entity<StackComponent> ent, ref ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (!args.IsInDetailsRange)
|
if (!args.IsInDetailsRange)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
args.PushMarkup(
|
args.PushMarkup(
|
||||||
Loc.GetString("comp-stack-examine-detail-count",
|
Loc.GetString("comp-stack-examine-detail-count",
|
||||||
("count", component.Count),
|
("count", ent.Comp.Count),
|
||||||
("markupCountColor", "lightgray")
|
("markupCountColor", "lightgray")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -415,7 +170,7 @@ namespace Content.Shared.Stacks
|
|||||||
The easiest and safest option is and always will be Option 1 otherwise we risk reagent deletion or duplication.
|
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.
|
That is why we cancel if we cannot set the minimum to the entire volume of the solution.
|
||||||
*/
|
*/
|
||||||
if(args.TryNewMinimum(sol.Volume))
|
if (args.TryNewMinimum(sol.Volume))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
args.Cancelled = true;
|
args.Cancelled = true;
|
||||||
@@ -423,7 +178,7 @@ namespace Content.Shared.Stacks
|
|||||||
|
|
||||||
private void OnEaten(Entity<StackComponent> eaten, ref IngestedEvent args)
|
private void OnEaten(Entity<StackComponent> eaten, ref IngestedEvent args)
|
||||||
{
|
{
|
||||||
if (!Use(eaten, 1))
|
if (!TryUse(eaten.AsNullable(), 1))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// We haven't eaten the whole stack yet or are unable to eat it completely.
|
// We haven't eaten the whole stack yet or are unable to eat it completely.
|
||||||
@@ -437,16 +192,18 @@ namespace Content.Shared.Stacks
|
|||||||
args.Destroy = true;
|
args.Destroy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args)
|
private void OnStackAlternativeInteract(Entity<StackComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||||
{
|
{
|
||||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null || stack.Count == 1)
|
if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Count == 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var user = args.User; // Can't pass ref events into verbs
|
||||||
|
|
||||||
AlternativeVerb halve = new()
|
AlternativeVerb halve = new()
|
||||||
{
|
{
|
||||||
Text = Loc.GetString("comp-stack-split-halve"),
|
Text = Loc.GetString("comp-stack-split-halve"),
|
||||||
Category = VerbCategory.Split,
|
Category = VerbCategory.Split,
|
||||||
Act = () => UserSplit(uid, args.User, stack.Count / 2, stack),
|
Act = () => UserSplit(ent, user, ent.Comp.Count / 2),
|
||||||
Priority = 1
|
Priority = 1
|
||||||
};
|
};
|
||||||
args.Verbs.Add(halve);
|
args.Verbs.Add(halve);
|
||||||
@@ -454,14 +211,14 @@ namespace Content.Shared.Stacks
|
|||||||
var priority = 0;
|
var priority = 0;
|
||||||
foreach (var amount in DefaultSplitAmounts)
|
foreach (var amount in DefaultSplitAmounts)
|
||||||
{
|
{
|
||||||
if (amount >= stack.Count)
|
if (amount >= ent.Comp.Count)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AlternativeVerb verb = new()
|
AlternativeVerb verb = new()
|
||||||
{
|
{
|
||||||
Text = amount.ToString(),
|
Text = amount.ToString(),
|
||||||
Category = VerbCategory.Split,
|
Category = VerbCategory.Split,
|
||||||
Act = () => UserSplit(uid, args.User, amount, stack),
|
Act = () => UserSplit(ent, user, amount),
|
||||||
// we want to sort by size, not alphabetically by the verb text.
|
// we want to sort by size, not alphabetically by the verb text.
|
||||||
Priority = priority
|
Priority = priority
|
||||||
};
|
};
|
||||||
@@ -479,19 +236,17 @@ namespace Content.Shared.Stacks
|
|||||||
/// This empty virtual method allows for UserSplit() to be called on the server from the client.
|
/// 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)
|
/// When prediction is improved, those two methods should be moved to shared, in order to predict the splitting itself (not just the verbs)
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual void UserSplit(EntityUid uid, EntityUid userUid, int amount,
|
protected virtual void UserSplit(Entity<StackComponent> stack, Entity<TransformComponent?> user, int amount)
|
||||||
StackComponent? stack = null,
|
|
||||||
TransformComponent? userTransform = null)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised when a stack's count has changed.
|
/// Event raised when a stack's count has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class StackCountChangedEvent : EntityEventArgs
|
public sealed class StackCountChangedEvent : EntityEventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The old stack count.
|
/// The old stack count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -507,5 +262,4 @@ namespace Content.Shared.Stacks
|
|||||||
OldCount = oldCount;
|
OldCount = oldCount;
|
||||||
NewCount = newCount;
|
NewCount = newCount;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,59 @@
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
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]
|
/// <summary>
|
||||||
public sealed partial class StackComponent : Component
|
/// What stack type we are.
|
||||||
{
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[DataField("stackType", required: true)]
|
||||||
[DataField("stackType", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<StackPrototype>))]
|
public ProtoId<StackPrototype> StackTypeId = default!;
|
||||||
public string StackTypeId { get; private set; } = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current stack count.
|
/// Current stack count.
|
||||||
/// Do NOT set this directly, use the <see cref="SharedStackSystem.SetCount"/> method instead.
|
/// Do NOT set this directly, use the <see cref="SharedStackSystem.SetCount"/> method instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("count")]
|
[DataField]
|
||||||
public int Count { get; set; } = 30;
|
public int Count = 30;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Max amount of things that can be in the stack.
|
/// Max amount of things that can be in the stack.
|
||||||
/// Overrides the max defined on the stack prototype.
|
/// Overrides the max defined on the stack prototype.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadOnly)]
|
[DataField]
|
||||||
[DataField("maxCountOverride")]
|
public int? MaxCountOverride;
|
||||||
public int? MaxCountOverride { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set to true to not reduce the count when used.
|
/// 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>
|
/// </summary>
|
||||||
[DataField("unlimited")]
|
[DataField]
|
||||||
[ViewVariables(VVAccess.ReadOnly)]
|
public bool Unlimited;
|
||||||
public bool Unlimited { get; set; }
|
|
||||||
|
|
||||||
[DataField("throwIndividually"), ViewVariables(VVAccess.ReadWrite)]
|
/// <summary>
|
||||||
public bool ThrowIndividually { get; set; } = false;
|
/// 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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by StackStatusControl in client to update UI.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
|
[Access(typeof(SharedStackSystem), Other = AccessPermissions.ReadWrite)] // Set by StackStatusControl
|
||||||
public bool UiUpdateNeeded { get; set; }
|
public bool UiUpdateNeeded { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default IconLayer stack.
|
/// Default IconLayer stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("baseLayer")]
|
[DataField]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string BaseLayer = "";
|
public string BaseLayer = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -61,15 +70,13 @@ namespace Content.Shared.Stacks
|
|||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("composite")]
|
[DataField("composite")]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool IsComposite;
|
public bool IsComposite;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sprite layers used in stack visualizer. Sprites first in layer correspond to lower stack states
|
/// 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>.
|
/// e.g. <code>_spriteLayers[0]</code> is lower stack level than <code>_spriteLayers[1]</code>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("layerStates")]
|
[DataField]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public List<string> LayerStates = new();
|
public List<string> LayerStates = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -78,24 +85,26 @@ namespace Content.Shared.Stacks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public StackLayerFunction LayerFunction = StackLayerFunction.None;
|
public StackLayerFunction LayerFunction = StackLayerFunction.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class StackComponentState : ComponentState
|
public sealed class StackComponentState : ComponentState
|
||||||
{
|
{
|
||||||
public int Count { get; }
|
public int Count { get; }
|
||||||
public int? MaxCount { get; }
|
public int? MaxCountOverride { get; }
|
||||||
|
public bool Unlimited { get; }
|
||||||
|
|
||||||
public StackComponentState(int count, int? maxCount)
|
public StackComponentState(int count, int? maxCountOverride, bool unlimited)
|
||||||
{
|
{
|
||||||
Count = count;
|
Count = count;
|
||||||
MaxCount = maxCount;
|
MaxCountOverride = maxCountOverride;
|
||||||
}
|
Unlimited = unlimited;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public enum StackLayerFunction : byte
|
public enum StackLayerFunction : byte
|
||||||
{
|
{
|
||||||
// <summary>
|
// <summary>
|
||||||
// No operation performed.
|
// No operation performed.
|
||||||
// </summary>
|
// </summary>
|
||||||
@@ -106,5 +115,4 @@ namespace Content.Shared.Stacks
|
|||||||
// Expects entity to have StackLayerThresholdComponent.
|
// Expects entity to have StackLayerThresholdComponent.
|
||||||
// </summary>
|
// </summary>
|
||||||
Threshold
|
Threshold
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ using Robust.Shared.Utility;
|
|||||||
|
|
||||||
namespace Content.Shared.Stacks;
|
namespace Content.Shared.Stacks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prototype used to combine and spawn like-entities for <see cref="SharedStackSystem"/>.
|
||||||
|
/// </summary>
|
||||||
[Prototype]
|
[Prototype]
|
||||||
public sealed partial class StackPrototype : IPrototype, IInheritingPrototype
|
public sealed partial class StackPrototype : IPrototype, IInheritingPrototype
|
||||||
{
|
{
|
||||||
@@ -37,12 +40,11 @@ public sealed partial class StackPrototype : IPrototype, IInheritingPrototype
|
|||||||
/// 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>
|
/// </summary>
|
||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public EntProtoId Spawn { get; private set; } = string.Empty;
|
public EntProtoId<StackComponent> Spawn { get; private set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum amount of things that can be in a stack.
|
/// The maximum amount of things that can be in a stack, can be overriden on <see cref="StackComponent"/>.
|
||||||
/// Can be overriden on <see cref="StackComponent"/>
|
/// If null, simply has unlimited max count.
|
||||||
/// if null, simply has unlimited max count.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public int? MaxCount { get; private set; }
|
public int? MaxCount { get; private set; }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Content.Shared.Stacks
|
|||||||
Actual,
|
Actual,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total amount of elements in the stack. If unspecified, the visualizer assumes
|
/// The total amount of elements in the stack. If unspecified, the visualizer assumes
|
||||||
/// its
|
/// it's StackComponent.LayerStates.Count
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MaxCount,
|
MaxCount,
|
||||||
Hide
|
Hide
|
||||||
|
|||||||
@@ -1219,7 +1219,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
|||||||
if (!_stackQuery.TryGetComponent(ent, out var containedStack))
|
if (!_stackQuery.TryGetComponent(ent, out var containedStack))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack))
|
if (!_stack.TryMergeStacks((insertEnt, insertStack), (ent, containedStack), out var _))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
stackedEntity = ent;
|
stackedEntity = ent;
|
||||||
@@ -1773,7 +1773,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
|||||||
return GetCumulativeItemAreas(uid) < uid.Comp.Grid.GetArea() || HasSpaceInStacks(uid);
|
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))
|
if (!Resolve(uid, ref uid.Comp))
|
||||||
return false;
|
return false;
|
||||||
@@ -1783,7 +1783,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
|||||||
if (!_stackQuery.TryGetComponent(contained, out var stack))
|
if (!_stackQuery.TryGetComponent(contained, out var stack))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (stackType != null && !stack.StackTypeId.Equals(stackType))
|
if (stackType != null && stack.StackTypeId != stackType)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (_stack.GetAvailableSpace(stack) == 0)
|
if (_stack.GetAvailableSpace(stack) == 0)
|
||||||
|
|||||||
@@ -23,18 +23,18 @@ public sealed partial class CurrencyPrototype : IPrototype
|
|||||||
/// doesn't necessarily refer to the full name of the currency, only
|
/// doesn't necessarily refer to the full name of the currency, only
|
||||||
/// that which is displayed to the user.
|
/// that which is displayed to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("displayName")]
|
[DataField]
|
||||||
public string DisplayName { get; private set; } = string.Empty;
|
public string DisplayName { get; private set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The physical entity of the currency
|
/// The physical entity of the currency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("cash", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<FixedPoint2, EntityPrototype>))]
|
[DataField]
|
||||||
public Dictionary<FixedPoint2, string>? Cash { get; private set; }
|
public Dictionary<FixedPoint2, EntProtoId>? Cash { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not this currency can be withdrawn from a shop by a player. Requires a valid entityId.
|
/// Whether or not this currency can be withdrawn from a shop by a player. Requires a valid entityId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("canWithdraw")]
|
[DataField]
|
||||||
public bool CanWithdraw { get; private set; } = true;
|
public bool CanWithdraw { get; private set; } = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ public sealed class FloorTileSystem : EntitySystem
|
|||||||
|
|
||||||
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
|
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
|
||||||
{
|
{
|
||||||
if (!_stackSystem.Use(uid, 1, stack))
|
if (!_stackSystem.TryUse((uid, stack), 1))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
PlaceAt(args.User, gridUid, mapGrid, location, currentTileDefinition.TileId, component.PlaceTileSound);
|
PlaceAt(args.User, gridUid, mapGrid, location, currentTileDefinition.TileId, component.PlaceTileSound);
|
||||||
@@ -154,7 +154,7 @@ public sealed class FloorTileSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
|
else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
|
||||||
{
|
{
|
||||||
if (!_stackSystem.Use(uid, 1, stack))
|
if (!_stackSystem.TryUse((uid, stack), 1))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user