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:
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -8,148 +7,246 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.Stack
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity system that handles everything relating to stacks.
|
||||
/// This is a good example for learning how to code in an ECS manner.
|
||||
/// Entity system that handles everything relating to stacks.
|
||||
/// This is a good example for learning how to code in an ECS manner.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class StackSystem : SharedStackSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public override void SetCount(EntityUid uid, int amount, StackComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
||||
base.SetCount(uid, amount, component);
|
||||
|
||||
// Queue delete stack if count reaches zero.
|
||||
if (component.Count <= 0)
|
||||
QueueDel(uid);
|
||||
}
|
||||
#region Spawning
|
||||
|
||||
/// <summary>
|
||||
/// Try to split this stack into two. Returns a non-null <see cref="Robust.Shared.GameObjects.EntityUid"/> if successful.
|
||||
/// Spawns a new entity and moves an amount to it from the stack.
|
||||
/// Moves nothing if amount is greater than ent's stack count.
|
||||
/// </summary>
|
||||
public EntityUid? Split(EntityUid uid, int amount, EntityCoordinates spawnPosition, StackComponent? stack = null)
|
||||
/// <param name="amount"> How much to move to the new entity. </param>
|
||||
/// <returns>Null if StackComponent doesn't resolve, or amount to move is greater than ent has available.</returns>
|
||||
[PublicAPI]
|
||||
public EntityUid? Split(Entity<StackComponent?> ent, int amount, EntityCoordinates spawnPosition)
|
||||
{
|
||||
if (!Resolve(uid, ref stack))
|
||||
if (!Resolve(ent.Owner, ref ent.Comp))
|
||||
return null;
|
||||
|
||||
// Try to remove the amount of things we want to split from the original stack...
|
||||
if (!Use(uid, amount, stack))
|
||||
if (!TryUse(ent, amount))
|
||||
return null;
|
||||
|
||||
// Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked...
|
||||
var prototype = _prototypeManager.TryIndex<StackPrototype>(stack.StackTypeId, out var stackType)
|
||||
? stackType.Spawn.ToString()
|
||||
: Prototype(uid)?.ID;
|
||||
if (!_prototypeManager.Resolve(ent.Comp.StackTypeId, out var stackType))
|
||||
return null;
|
||||
|
||||
// Set the output parameter in the event instance to the newly split stack.
|
||||
var entity = Spawn(prototype, spawnPosition);
|
||||
var newEntity = SpawnAtPosition(stackType.Spawn, spawnPosition);
|
||||
|
||||
if (TryComp(entity, out StackComponent? stackComp))
|
||||
{
|
||||
// Set the split stack's count.
|
||||
SetCount(entity, amount, stackComp);
|
||||
// Don't let people dupe unlimited stacks
|
||||
stackComp.Unlimited = false;
|
||||
}
|
||||
// There should always be a StackComponent
|
||||
var stackComp = Comp<StackComponent>(newEntity);
|
||||
|
||||
var ev = new StackSplitEvent(entity);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
SetCount((newEntity, stackComp), amount);
|
||||
stackComp.Unlimited = false; // Don't let people dupe unlimited stacks
|
||||
Dirty(newEntity, stackComp);
|
||||
|
||||
var ev = new StackSplitEvent(newEntity);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
#region SpawnAtPosition
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
|
||||
/// </summary>
|
||||
public EntityUid Spawn(int amount, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
|
||||
/// <inheritdoc cref="SpawnAtPosition(int, StackPrototype, EntityCoordinates)"/>
|
||||
[PublicAPI]
|
||||
public EntityUid SpawnAtPosition(int count, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
|
||||
{
|
||||
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"/>.
|
||||
/// Say you want to spawn 97 units of something that has a max stack count of 30.
|
||||
/// This would spawn 3 stacks of 30 and 1 stack of 7.
|
||||
/// </summary>
|
||||
public EntityUid Spawn(int amount, StackPrototype prototype, 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)
|
||||
{
|
||||
// Set the output result parameter to the new stack entity...
|
||||
var entity = SpawnAtPosition(prototype.Spawn, spawnPosition);
|
||||
var stack = Comp<StackComponent>(entity);
|
||||
if (amounts.Count <= 0)
|
||||
{
|
||||
Log.Error(
|
||||
$"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
|
||||
return new();
|
||||
}
|
||||
|
||||
// And finally, set the correct amount!
|
||||
SetCount(entity, amount, stack);
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
foreach (var count in amounts)
|
||||
{
|
||||
var entity = SpawnAtPosition(entityPrototype, spawnPosition); // The real SpawnAtPosition
|
||||
spawnedEnts.Add(entity);
|
||||
if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
|
||||
SetCount((entity, stackComp), count);
|
||||
}
|
||||
|
||||
return spawnedEnts;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
|
||||
[PublicAPI]
|
||||
public List<EntityUid> SpawnMultipleAtPosition(EntProtoId entityPrototypeId,
|
||||
int amount,
|
||||
EntityCoordinates spawnPosition)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Say you want to spawn 97 units of something that has a max stack count of 30.
|
||||
/// This would spawn 3 stacks of 30 and 1 stack of 7.
|
||||
/// </summary>
|
||||
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition)
|
||||
/// <inheritdoc cref="SpawnNextToOrDrop(int, StackPrototype, EntityUid)"/>
|
||||
[PublicAPI]
|
||||
public EntityUid SpawnNextToOrDrop(int amount, ProtoId<StackPrototype> id, EntityUid source)
|
||||
{
|
||||
if (amount <= 0)
|
||||
var proto = _prototypeManager.Index(id);
|
||||
return SpawnNextToOrDrop(amount, proto, source);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
|
||||
private List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId entityPrototype,
|
||||
List<int> amounts,
|
||||
EntityUid target)
|
||||
{
|
||||
if (amounts.Count <= 0)
|
||||
{
|
||||
Log.Error(
|
||||
$"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
|
||||
$"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
|
||||
return new();
|
||||
}
|
||||
|
||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
||||
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
foreach (var count in spawns)
|
||||
foreach (var count in amounts)
|
||||
{
|
||||
var entity = SpawnAtPosition(entityPrototype, spawnPosition);
|
||||
var entity = SpawnNextToOrDrop(entityPrototype, target); // The real SpawnNextToOrDrop
|
||||
spawnedEnts.Add(entity);
|
||||
SetCount(entity, count);
|
||||
if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
|
||||
SetCount((entity, stackComp), count);
|
||||
}
|
||||
|
||||
return spawnedEnts;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultiple(string,int,EntityCoordinates)"/>
|
||||
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityUid target)
|
||||
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
|
||||
[PublicAPI]
|
||||
public List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId stack,
|
||||
int amount,
|
||||
EntityUid target)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
Log.Error(
|
||||
$"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
|
||||
return new();
|
||||
}
|
||||
|
||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
||||
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
foreach (var count in spawns)
|
||||
{
|
||||
var entity = SpawnNextToOrDrop(entityPrototype, target);
|
||||
spawnedEnts.Add(entity);
|
||||
SetCount(entity, count);
|
||||
}
|
||||
|
||||
return spawnedEnts;
|
||||
return SpawnMultipleNextToOrDrop(stack,
|
||||
CalculateSpawns(stack, amount),
|
||||
target);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
|
||||
[PublicAPI]
|
||||
public List<EntityUid> SpawnMultipleNextToOrDrop(EntityPrototype stack,
|
||||
int amount,
|
||||
EntityUid target)
|
||||
{
|
||||
return SpawnMultipleNextToOrDrop(stack.ID,
|
||||
CalculateSpawns(stack, amount),
|
||||
target);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
|
||||
[PublicAPI]
|
||||
public List<EntityUid> SpawnMultipleNextToOrDrop(StackPrototype stack,
|
||||
int amount,
|
||||
EntityUid target)
|
||||
{
|
||||
return SpawnMultipleNextToOrDrop(stack.Spawn,
|
||||
CalculateSpawns(stack, amount),
|
||||
target);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
|
||||
[PublicAPI]
|
||||
public List<EntityUid> SpawnMultipleNextToOrDrop(ProtoId<StackPrototype> stackId,
|
||||
int amount,
|
||||
EntityUid target)
|
||||
{
|
||||
var stackProto = _prototypeManager.Index(stackId);
|
||||
return SpawnMultipleNextToOrDrop(stackProto.Spawn,
|
||||
CalculateSpawns(stackProto, amount),
|
||||
target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Calculate
|
||||
|
||||
/// <summary>
|
||||
/// Calculates how many stacks to spawn that total up to <paramref name="amount"/>.
|
||||
/// </summary>
|
||||
/// <param name="entityPrototype">The stack to spawn.</param>
|
||||
/// <param name="amount">The amount of pieces across all stacks.</param>
|
||||
/// <returns>The list of stack counts per entity.</returns>
|
||||
private List<int> CalculateSpawns(string entityPrototype, int amount)
|
||||
private List<int> CalculateSpawns(int maxCountPerStack, int amount)
|
||||
{
|
||||
var proto = _prototypeManager.Index<EntityPrototype>(entityPrototype);
|
||||
proto.TryGetComponent<StackComponent>(out var stack, EntityManager.ComponentFactory);
|
||||
var maxCountPerStack = GetMaxCount(stack);
|
||||
var amounts = new List<int>();
|
||||
while (amount > 0)
|
||||
{
|
||||
@@ -161,28 +258,47 @@ namespace Content.Server.Stack
|
||||
return amounts;
|
||||
}
|
||||
|
||||
protected override void UserSplit(EntityUid uid, EntityUid userUid, int amount,
|
||||
StackComponent? stack = null,
|
||||
TransformComponent? userTransform = null)
|
||||
/// <inheritdoc cref="CalculateSpawns(int, int)"/>
|
||||
private List<int> CalculateSpawns(StackPrototype stackProto, int amount)
|
||||
{
|
||||
if (!Resolve(uid, ref stack))
|
||||
return;
|
||||
return CalculateSpawns(GetMaxCount(stackProto), amount);
|
||||
}
|
||||
|
||||
if (!Resolve(userUid, ref userTransform))
|
||||
/// <inheritdoc cref="CalculateSpawns(int, int)"/>
|
||||
private List<int> CalculateSpawns(EntityPrototype entityPrototype, int amount)
|
||||
{
|
||||
return CalculateSpawns(GetMaxCount(entityPrototype), amount);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateSpawns(int, int)"/>
|
||||
private List<int> CalculateSpawns(EntProtoId entityId, int amount)
|
||||
{
|
||||
return CalculateSpawns(GetMaxCount(entityId), amount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
#region Event Handlers
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UserSplit(Entity<StackComponent> stack, Entity<TransformComponent?> user, int amount)
|
||||
{
|
||||
if (!Resolve(user.Owner, ref user.Comp, false))
|
||||
return;
|
||||
|
||||
if (amount <= 0)
|
||||
{
|
||||
Popup.PopupCursor(Loc.GetString("comp-stack-split-too-small"), userUid, PopupType.Medium);
|
||||
Popup.PopupCursor(Loc.GetString("comp-stack-split-too-small"), user.Owner, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split)
|
||||
if (Split(stack.AsNullable(), amount, user.Comp.Coordinates) is not { } split)
|
||||
return;
|
||||
|
||||
Hands.PickupOrDrop(userUid, split);
|
||||
Hands.PickupOrDrop(user.Owner, split);
|
||||
|
||||
Popup.PopupCursor(Loc.GetString("comp-stack-split"), userUid);
|
||||
Popup.PopupCursor(Loc.GetString("comp-stack-split"), user.Owner);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user