Files
space-station-14/Content.Server/Stack/StackSystem.cs
ee618e3037 Add tile gun module (#41503)
* add tile gun module

* fix stacks being loaded all at once in guns

* fix tile gun module

* fixed indentation

* remove not necessary stuff

Co-authored-by: āda <ss.adasts@gmail.com>

* Use alias

Co-authored-by: āda <ss.adasts@gmail.com>

* fix OnBallisticInteractUsing

* forgot one

Co-authored-by: āda <ss.adasts@gmail.com>

* fix FillAmmo

* add another todo

* pass user instead of event args

Co-authored-by: āda <ss.adasts@gmail.com>

* fix last commit

* update todo

Co-authored-by: āda <ss.adasts@gmail.com>

* wrong place

* move gun to first slot

* custom sprite

* call a event

* important detail in the commentary

* remove recipe

* not used

* also not used anymore

* when did i remove this?

* fix sprite

* shoot faster

* move to shared

* use API method

* remove useless stuff

* Revert "remove useless stuff"

This reverts commit b198303aed.

* fix code

* shot sound

* change spread

* fix attribution

* load sound

* empty sound

* commentary

* more commentary fix

* rename event

* remove duplicate

* who knew

* turn to mono

* yet another beep

* increase volume

* make it a crowbar

* make it able to self load clicking tiles

* make it able to pry tiles

* who needs a crowbar anymore

* make it networked

* no more hands

* move BeforeAmmoLoadedEvent call to TryBallisticInsert

* before now

* change sprite

* slight update to sprite

* mag visuals

* move split back to server

* get one method

* remove subscription

* use API method in the gun system

* move API call up

Co-authored-by: āda <ss.adasts@gmail.com>

* remove unused stuff

* fix get one method

* comment change

* half the firerate

---------

Co-authored-by: āda <ss.adasts@gmail.com>
Co-authored-by: beck-thompson <beck314159@hotmail.com>
Co-authored-by: iaada <iaada@users.noreply.github.com>
2026-04-03 21:30:12 +00:00

307 lines
13 KiB
C#

using Content.Shared.Popups;
using Content.Shared.Stacks;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server.Stack
{
/// <summary>
/// Entity system that handles everything relating to stacks.
/// This is a good example for learning how to code in an ECS manner.
/// </summary>
[UsedImplicitly]
public sealed class StackSystem : SharedStackSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
#region Spawning
/// <summary>
/// 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>
/// <param name="ent">Entity to split in a new stack.</param>
/// <param name="amount">How much to move to the new entity.</param>
/// <param name="spawnPosition">Where to spawn the new stack</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(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<StackComponent>(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
/// <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;
}
/// <inheritdoc cref="SpawnAtPosition(int, StackPrototype, EntityCoordinates)"/>
[PublicAPI]
public EntityUid SpawnAtPosition(int count, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
{
var proto = _prototypeManager.Index(id);
return SpawnAtPosition(count, proto, spawnPosition);
}
/// <summary>
/// Say you want to spawn 97 units of something that has a max stack count of 30.
/// This would spawn 3 stacks of 30 and 1 stack of 7.
/// </summary>
/// <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 (amounts.Count <= 0)
{
Log.Error(
$"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
return new();
}
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;
}
/// <inheritdoc cref="SpawnNextToOrDrop(int, StackPrototype, EntityUid)"/>
[PublicAPI]
public EntityUid SpawnNextToOrDrop(int amount, ProtoId<StackPrototype> id, EntityUid source)
{
var proto = _prototypeManager.Index(id);
return SpawnNextToOrDrop(amount, proto, source);
}
/// <inheritdoc cref="SpawnMultipleAtPosition(EntProtoId, List{int}, EntityCoordinates)"/>
private List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId entityPrototype,
List<int> amounts,
EntityUid target)
{
if (amounts.Count <= 0)
{
Log.Error(
$"Attempted to spawn stacks of nothing: {entityPrototype}, {amounts}. Trace: {Environment.StackTrace}");
return new();
}
var spawnedEnts = new List<EntityUid>();
foreach (var count in amounts)
{
var entity = SpawnNextToOrDrop(entityPrototype, target); // The real SpawnNextToOrDrop
spawnedEnts.Add(entity);
if (TryComp<StackComponent>(entity, out var stackComp)) // prevent errors from the Resolve
SetCount((entity, stackComp), count);
}
return spawnedEnts;
}
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
[PublicAPI]
public List<EntityUid> SpawnMultipleNextToOrDrop(EntProtoId stack,
int amount,
EntityUid target)
{
return SpawnMultipleNextToOrDrop(stack,
CalculateSpawns(stack, amount),
target);
}
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
[PublicAPI]
public List<EntityUid> SpawnMultipleNextToOrDrop(EntityPrototype stack,
int amount,
EntityUid target)
{
return SpawnMultipleNextToOrDrop(stack.ID,
CalculateSpawns(stack, amount),
target);
}
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
[PublicAPI]
public List<EntityUid> SpawnMultipleNextToOrDrop(StackPrototype stack,
int amount,
EntityUid target)
{
return SpawnMultipleNextToOrDrop(stack.Spawn,
CalculateSpawns(stack, amount),
target);
}
/// <inheritdoc cref="SpawnMultipleNextToOrDrop(EntProtoId, List{int}, EntityUid)"/>
[PublicAPI]
public List<EntityUid> SpawnMultipleNextToOrDrop(ProtoId<StackPrototype> stackId,
int amount,
EntityUid target)
{
var stackProto = _prototypeManager.Index(stackId);
return SpawnMultipleNextToOrDrop(stackProto.Spawn,
CalculateSpawns(stackProto, amount),
target);
}
#endregion
#region Calculate
/// <summary>
/// Calculates how many stacks to spawn that total up to <paramref name="amount"/>.
/// </summary>
/// <returns>The list of stack counts per entity.</returns>
private List<int> CalculateSpawns(int maxCountPerStack, int amount)
{
var amounts = new List<int>();
while (amount > 0)
{
var countAmount = Math.Min(maxCountPerStack, amount);
amount -= countAmount;
amounts.Add(countAmount);
}
return amounts;
}
/// <inheritdoc cref="CalculateSpawns(int, int)"/>
private List<int> CalculateSpawns(StackPrototype stackProto, int amount)
{
return CalculateSpawns(GetMaxCount(stackProto), amount);
}
/// <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"), 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
}
}