using Content.Shared.Popups; using Content.Shared.Stacks; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Server.Stack { /// /// Entity system that handles everything relating to stacks. /// This is a good example for learning how to code in an ECS manner. /// [UsedImplicitly] public sealed class StackSystem : SharedStackSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; #region Spawning /// /// Spawns a new entity and moves an amount to it from the stack. /// Moves nothing if amount is greater than ent's stack count. /// /// How much to move to the new entity. /// Null if StackComponent doesn't resolve, or amount to move is greater than ent has available. [PublicAPI] public EntityUid? Split(Entity ent, int amount, EntityCoordinates spawnPosition) { 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 (!TryUse(ent, amount)) return null; 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 newEntity = SpawnAtPosition(stackType.Spawn, spawnPosition); // There should always be a StackComponent var stackComp = Comp(newEntity); 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 /// /// Spawns a stack of a certain stack type and sets its count. Won't set the stack over its max. /// /// The amount to set the spawned stack to. [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; } /// [PublicAPI] public EntityUid SpawnAtPosition(int count, ProtoId id, EntityCoordinates spawnPosition) { var proto = _prototypeManager.Index(id); return SpawnAtPosition(count, proto, spawnPosition); } /// /// 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. /// /// The entities spawned. /// If the entity to spawn doesn't have stack component this will spawn a bunch of single items. private List SpawnMultipleAtPosition(EntProtoId entityPrototype, List amounts, EntityCoordinates spawnPosition) { if (amounts.Count <= 0) { Log.Error( $"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}"); return new(); } var spawnedEnts = new List(); foreach (var count in amounts) { var entity = SpawnAtPosition(entityPrototype, spawnPosition); // The real SpawnAtPosition spawnedEnts.Add(entity); if (TryComp(entity, out var stackComp)) // prevent errors from the Resolve SetCount((entity, stackComp), count); } return spawnedEnts; } /// [PublicAPI] public List SpawnMultipleAtPosition(EntProtoId entityPrototypeId, int amount, EntityCoordinates spawnPosition) { return SpawnMultipleAtPosition(entityPrototypeId, CalculateSpawns(entityPrototypeId, amount), spawnPosition); } /// [PublicAPI] public List SpawnMultipleAtPosition(EntityPrototype entityProto, int amount, EntityCoordinates spawnPosition) { return SpawnMultipleAtPosition(entityProto.ID, CalculateSpawns(entityProto, amount), spawnPosition); } /// [PublicAPI] public List SpawnMultipleAtPosition(StackPrototype stack, int amount, EntityCoordinates spawnPosition) { return SpawnMultipleAtPosition(stack.Spawn, CalculateSpawns(stack, amount), spawnPosition); } /// [PublicAPI] public List SpawnMultipleAtPosition(ProtoId stackId, int amount, EntityCoordinates spawnPosition) { var stackProto = _prototypeManager.Index(stackId); return SpawnMultipleAtPosition(stackProto.Spawn, CalculateSpawns(stackProto, amount), spawnPosition); } #endregion #region SpawnNextToOrDrop /// [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; } /// [PublicAPI] public EntityUid SpawnNextToOrDrop(int amount, ProtoId id, EntityUid source) { var proto = _prototypeManager.Index(id); return SpawnNextToOrDrop(amount, proto, source); } /// private List SpawnMultipleNextToOrDrop(EntProtoId entityPrototype, List amounts, EntityUid target) { if (amounts.Count <= 0) { Log.Error( $"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}"); return new(); } var spawnedEnts = new List(); foreach (var count in amounts) { var entity = SpawnNextToOrDrop(entityPrototype, target); // The real SpawnNextToOrDrop spawnedEnts.Add(entity); if (TryComp(entity, out var stackComp)) // prevent errors from the Resolve SetCount((entity, stackComp), count); } return spawnedEnts; } /// [PublicAPI] public List SpawnMultipleNextToOrDrop(EntProtoId stack, int amount, EntityUid target) { return SpawnMultipleNextToOrDrop(stack, CalculateSpawns(stack, amount), target); } /// [PublicAPI] public List SpawnMultipleNextToOrDrop(EntityPrototype stack, int amount, EntityUid target) { return SpawnMultipleNextToOrDrop(stack.ID, CalculateSpawns(stack, amount), target); } /// [PublicAPI] public List SpawnMultipleNextToOrDrop(StackPrototype stack, int amount, EntityUid target) { return SpawnMultipleNextToOrDrop(stack.Spawn, CalculateSpawns(stack, amount), target); } /// [PublicAPI] public List SpawnMultipleNextToOrDrop(ProtoId stackId, int amount, EntityUid target) { var stackProto = _prototypeManager.Index(stackId); return SpawnMultipleNextToOrDrop(stackProto.Spawn, CalculateSpawns(stackProto, amount), target); } #endregion #region Calculate /// /// Calculates how many stacks to spawn that total up to . /// /// The list of stack counts per entity. private List CalculateSpawns(int maxCountPerStack, int amount) { var amounts = new List(); while (amount > 0) { var countAmount = Math.Min(maxCountPerStack, amount); amount -= countAmount; amounts.Add(countAmount); } return amounts; } /// private List CalculateSpawns(StackPrototype stackProto, int amount) { return CalculateSpawns(GetMaxCount(stackProto), amount); } /// private List CalculateSpawns(EntityPrototype entityPrototype, int amount) { return CalculateSpawns(GetMaxCount(entityPrototype), amount); } /// private List CalculateSpawns(EntProtoId entityId, int amount) { return CalculateSpawns(GetMaxCount(entityId), amount); } #endregion #endregion #region Event Handlers /// protected override void UserSplit(Entity stack, Entity user, int amount) { if (!Resolve(user.Owner, ref user.Comp, false)) return; if (amount <= 0) { Popup.PopupCursor(Loc.GetString("comp-stack-split-too-small"), user.Owner, PopupType.Medium); return; } if (Split(stack.AsNullable(), amount, user.Comp.Coordinates) is not { } split) return; Hands.PickupOrDrop(user.Owner, split); Popup.PopupCursor(Loc.GetString("comp-stack-split"), user.Owner); } #endregion } }