From fecd60e98aac5b53585301b18b76993c37b30a52 Mon Sep 17 00:00:00 2001
From: Rane <60792108+Elijahrane@users.noreply.github.com>
Date: Sat, 7 Jan 2023 13:09:05 -0500
Subject: [PATCH] Generalized material spawning (#12489)
---
.../Tests/Materials/MaterialTests.cs | 59 +++++++++++++
Content.Server/Cloning/CloningSystem.cs | 9 +-
.../Cloning/Components/CloningPodComponent.cs | 6 --
.../Materials/MaterialStorageSystem.cs | 21 ++++-
.../BiomassReclaimerComponent.cs | 6 --
.../BiomassReclaimerSystem.cs | 2 +-
Content.Server/Stack/StackSystem.cs | 84 ++++++++++++++++---
.../Store/Systems/StoreSystem.Ui.cs | 2 +-
Content.Shared/Materials/MaterialPrototype.cs | 16 ++--
.../Entities/Objects/Misc/space_cash.yml | 2 +-
.../Prototypes/Reagents/Materials/glass.yml | 8 +-
.../Reagents/Materials/materials.yml | 14 ++--
.../Prototypes/Reagents/Materials/metals.yml | 8 +-
13 files changed, 177 insertions(+), 60 deletions(-)
create mode 100644 Content.IntegrationTests/Tests/Materials/MaterialTests.cs
diff --git a/Content.IntegrationTests/Tests/Materials/MaterialTests.cs b/Content.IntegrationTests/Tests/Materials/MaterialTests.cs
new file mode 100644
index 0000000000..1ee2d5bb95
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Materials/MaterialTests.cs
@@ -0,0 +1,59 @@
+#nullable enable
+using NUnit.Framework;
+using System.Threading.Tasks;
+using Content.Server.Stack;
+using Content.Shared.Stacks;
+using Content.Shared.Materials;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+
+///
+/// Materials and stacks have some odd relationships to entities,
+/// so we need some test coverage for them.
+///
+namespace Content.IntegrationTests.Tests.Materials
+{
+ [TestFixture]
+ [TestOf(typeof(StackSystem))]
+ [TestOf(typeof(MaterialPrototype))]
+ public sealed class MaterialPrototypeSpawnsStackMaterialTest
+ {
+ [Test]
+ public async Task MaterialPrototypeSpawnsStackMaterial()
+ {
+ await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
+ var server = pairTracker.Pair.Server;
+ await server.WaitIdleAsync();
+
+ var mapManager = server.ResolveDependency();
+ var prototypeManager = server.ResolveDependency();
+ var entityManager = server.ResolveDependency();
+
+ var testMap = await PoolManager.CreateTestMap(pairTracker);
+
+ await server.WaitAssertion(() =>
+ {
+ var allMaterialProtos = prototypeManager.EnumeratePrototypes();
+ var coords = testMap.GridCoords;
+
+ foreach (var proto in allMaterialProtos)
+ {
+ if (proto.StackEntity == "")
+ continue;
+
+ var spawned = entityManager.SpawnEntity(proto.StackEntity, coords);
+
+ Assert.That(entityManager.HasComponent(spawned),
+ $"{proto.ID} 'stack entity' {proto.StackEntity} has the stack component");
+
+ Assert.That(entityManager.HasComponent(spawned),
+ $"{proto.ID} 'material stack' {proto.StackEntity} has the material component");
+ }
+
+ mapManager.DeleteMap(testMap.MapId);
+ });
+ }
+ }
+}
diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs
index e4852837e2..59b96d612f 100644
--- a/Content.Server/Cloning/CloningSystem.cs
+++ b/Content.Server/Cloning/CloningSystem.cs
@@ -67,7 +67,6 @@ namespace Content.Server.Cloning
SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnPartsRefreshed);
SubscribeLocalEvent(OnUpgradeExamine);
- SubscribeLocalEvent(OnDeconstruct);
SubscribeLocalEvent(Reset);
SubscribeLocalEvent(HandleMindAdded);
SubscribeLocalEvent(OnPortDisconnected);
@@ -96,11 +95,6 @@ namespace Content.Server.Cloning
args.AddPercentageUpgrade("cloning-pod-component-upgrade-biomass-requirement", component.BiomassRequirementMultiplier);
}
- private void OnDeconstruct(EntityUid uid, CloningPodComponent component, MachineDeconstructedEvent args)
- {
- _serverStackSystem.SpawnMultiple(component.MaterialCloningOutput, _material.GetMaterialAmount(uid, component.RequiredMaterial), Transform(uid).Coordinates);
- }
-
internal void TransferMindToClone(Mind.Mind mind)
{
if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) ||
@@ -322,8 +316,7 @@ namespace Content.Server.Cloning
}
_spillableSystem.SpillAt(uid, bloodSolution, "PuddleBlood");
- var biomassStack = Spawn(clonePod.MaterialCloningOutput, transform.Coordinates);
- _stackSystem.SetCount(biomassStack, _robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)));
+ _serverStackSystem.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
clonePod.UsedBiomass = 0;
RemCompDeferred(uid);
diff --git a/Content.Server/Cloning/Components/CloningPodComponent.cs b/Content.Server/Cloning/Components/CloningPodComponent.cs
index 1f564509e6..f662fb42ba 100644
--- a/Content.Server/Cloning/Components/CloningPodComponent.cs
+++ b/Content.Server/Cloning/Components/CloningPodComponent.cs
@@ -33,12 +33,6 @@ namespace Content.Server.Cloning.Components
[DataField("requiredMaterial", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)]
public string RequiredMaterial = "Biomass";
- ///
- /// The entity that is spawned on machine deconstruct as well as failed cloning.
- ///
- [DataField("materialCloningOutput", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public string MaterialCloningOutput = "MaterialBiomass";
-
///
/// The base amount of time it takes to clone a body
///
diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs
index 0f81da4a38..4de304a7f1 100644
--- a/Content.Server/Materials/MaterialStorageSystem.cs
+++ b/Content.Server/Materials/MaterialStorageSystem.cs
@@ -1,9 +1,11 @@
using Content.Server.Administration.Logs;
using Content.Shared.Materials;
using Content.Shared.Popups;
-using Content.Server.Power.Components;
-using Content.Shared.Database;
using Content.Shared.Stacks;
+using Content.Server.Power.Components;
+using Content.Server.Construction.Components;
+using Content.Server.Stack;
+using Content.Shared.Database;
namespace Content.Server.Materials;
@@ -15,6 +17,21 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly StackSystem _stackSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnDeconstructed);
+ }
+
+ private void OnDeconstructed(EntityUid uid, MaterialStorageComponent component, MachineDeconstructedEvent args)
+ {
+ foreach (var (material, amount) in component.Storage)
+ {
+ _stackSystem.SpawnMultipleFromMaterial(amount, material, Transform(uid).Coordinates);
+ }
+ }
public override bool TryInsertMaterialEntity(EntityUid user, EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? component = null)
{
diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs
index b0117ef2ed..5f2c6139d2 100644
--- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs
+++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs
@@ -55,12 +55,6 @@ namespace Content.Server.Medical.BiomassReclaimer
[ViewVariables(VVAccess.ReadWrite)]
public float YieldPerUnitMass = default;
- ///
- /// The entity that is output by the reclaimer
- ///
- [DataField("outputEntityId", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public string OutputEntityId = "MaterialBiomass";
-
///
/// The base yield per mass unit when no components are upgraded.
///
diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs
index 988b9391ca..efb0c9d03a 100644
--- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs
+++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs
@@ -76,7 +76,7 @@ namespace Content.Server.Medical.BiomassReclaimer
continue;
}
- _stackSystem.SpawnMultiple(reclaimer.OutputEntityId, reclaimer.CurrentExpectedYield, Transform(reclaimer.Owner).Coordinates);
+ _stackSystem.SpawnMultipleFromMaterial((int) reclaimer.CurrentExpectedYield, "Biomass", Transform(reclaimer.Owner).Coordinates);
reclaimer.BloodReagent = null;
reclaimer.SpawnedEntities.Clear();
diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs
index 6db0ec1efe..5c7a7709e3 100644
--- a/Content.Server/Stack/StackSystem.cs
+++ b/Content.Server/Stack/StackSystem.cs
@@ -1,9 +1,9 @@
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
+using Content.Shared.Materials;
using JetBrains.Annotations;
using Robust.Shared.Map;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Stack
@@ -16,6 +16,7 @@ namespace Content.Server.Stack
public sealed class StackSystem : SharedStackSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly SharedStackSystem _sharedStack = default!;
public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
@@ -87,24 +88,81 @@ namespace Content.Server.Stack
}
///
- /// Say you want to spawn 97 stacks 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.
///
- public List SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition)
+ public List SpawnMultiple(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
{
- var proto = _prototypeManager.Index(entityPrototype);
- proto.TryGetComponent(out var stack);
- var maxCountPerStack = GetMaxCount(stack);
- var spawnedEnts = new List();
+ var list = new List();
+ if (amount <= 0)
+ return list;
+
+ // At least 1 is being spawned, we'll use the first to extract otherwise inaccessible information
+ // ??TODO??: Indexing the entity proto and extracting from its component registry could possibly be better?
+ // it doesn't look like it would save LOC even compressing this to a single loop and I'm not sure what other issues it might introduce
+ var firstSpawn = Spawn(materialProto.StackEntity, coordinates);
+ list.Add(firstSpawn);
+
+ if (!TryComp(firstSpawn, out var stack) || stack.StackTypeId == null)
+ return list;
+
+ if (!TryComp(firstSpawn, out var material))
+ return list;
+
+ int maxCountPerStack = _sharedStack.GetMaxCount(stack);
+ var materialPerStack = material._materials[materialProto.ID];
+
+ var materialPerMaxCount = maxCountPerStack * materialPerStack;
+
+ // no material duping for you
+ if (amount < materialPerStack)
+ {
+ Del(firstSpawn);
+ return list;
+ }
+
+ if (amount > materialPerMaxCount)
+ {
+ SetCount(firstSpawn, maxCountPerStack, stack);
+ amount -= materialPerMaxCount;
+ } else
+ {
+ SetCount(firstSpawn, (amount / materialPerStack), stack);
+ amount = 0;
+ }
+
while (amount > 0)
{
- var entity = Spawn(entityPrototype, spawnPosition);
- spawnedEnts.Add(entity);
- var countAmount = Math.Min(maxCountPerStack, amount);
- SetCount(entity, countAmount);
- amount -= countAmount;
+ var entity = Spawn(materialProto.StackEntity, coordinates);
+ list.Add(entity);
+ var nextStack = Comp(entity);
+ if (amount > materialPerMaxCount)
+ {
+ SetCount(entity, materialPerMaxCount, nextStack);
+ amount -= materialPerMaxCount;
+ }
+ else
+ {
+ SetCount(entity, (amount / materialPerStack), nextStack);
+ amount = 0;
+ }
}
- return spawnedEnts;
+ return list;
+ }
+
+ ///
+ /// Spawn an amount of a material in stack entities. Note the 'amount' is material dependent. 1 biomass = 1 biomass in its stack,
+ /// but 100 plasma = 1 sheet of plasma, etc.
+ ///
+ public List SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
+ {
+ if (!_prototypeManager.TryIndex(material, out var stackType))
+ {
+ Logger.Error("Failed to index material prototype " + material);
+ return new List();
+ }
+
+ return SpawnMultiple(amount, stackType, coordinates);
}
private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent args)
diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs
index f7a095f1be..c0f5ecd445 100644
--- a/Content.Server/Store/Systems/StoreSystem.Ui.cs
+++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs
@@ -200,7 +200,7 @@ public sealed partial class StoreSystem : EntitySystem
{
var cashId = proto.Cash[value];
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value));
- var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coordinates);
+ var ents = _stack.SpawnMultipleFromMaterial(amountToSpawn, cashId, coordinates);
_hands.PickupOrDrop(buyer, ents.First());
amountRemaining -= value * amountToSpawn;
}
diff --git a/Content.Shared/Materials/MaterialPrototype.cs b/Content.Shared/Materials/MaterialPrototype.cs
index ff94f409f1..aab299cfb2 100644
--- a/Content.Shared/Materials/MaterialPrototype.cs
+++ b/Content.Shared/Materials/MaterialPrototype.cs
@@ -1,8 +1,7 @@
-using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
-using Robust.Shared.Utility;
namespace Content.Shared.Materials
{
@@ -13,8 +12,6 @@ namespace Content.Shared.Materials
[Prototype("material")]
public sealed class MaterialPrototype : IPrototype, IInheritingPrototype
{
- private string _name = string.Empty;
-
[ViewVariables]
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer))]
public string[]? Parents { get; }
@@ -27,11 +24,16 @@ namespace Content.Shared.Materials
[IdDataFieldAttribute]
public string ID { get; } = default!;
- [DataField("stack", customTypeSerializer:typeof(PrototypeIdSerializer))]
- public string? StackId { get; } = null;
+ ///
+ /// For material storage to be able to convert back and forth
+ /// between the material and physical entities you can carry,
+ /// include which stack we should spawn by default.
+ ///
+ [DataField("stackEntity", customTypeSerializer:typeof(PrototypeIdSerializer))]
+ public string StackEntity { get; } = "";
[DataField("name")]
- public string Name { get; private set; } = "";
+ public string Name = "";
[DataField("color")]
public Color Color { get; } = Color.Gray;
diff --git a/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml b/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml
index 56684bd460..4b786e2dc8 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml
@@ -44,7 +44,7 @@
- type: material
id: Credit
name: spacebuck
- stack: Credit
+ stackEntity: SpaceCash
icon: { sprite: /Textures/Objects/Economy/cash.rsi, state: cash }
price: 1
diff --git a/Resources/Prototypes/Reagents/Materials/glass.yml b/Resources/Prototypes/Reagents/Materials/glass.yml
index d8e5734cce..7225ef6801 100644
--- a/Resources/Prototypes/Reagents/Materials/glass.yml
+++ b/Resources/Prototypes/Reagents/Materials/glass.yml
@@ -1,6 +1,6 @@
- type: material
id: Glass
- stack: Glass
+ stackEntity: SheetGlass
name: materials-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: glass }
color: "#a8ccd7"
@@ -8,7 +8,7 @@
- type: material
id: ReinforcedGlass
- stack: ReinforcedGlass
+ stackEntity: SheetRGlass
name: materials-reinforced-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rglass }
color: "#549bb0"
@@ -16,7 +16,7 @@
- type: material
id: PlasmaGlass
- stack: PlasmaGlass
+ stackEntity: SheetPGlass
name: materials-plasma-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: pglass }
color: "#b35989"
@@ -24,7 +24,7 @@
- type: material
id: ReinforcedPlasmaGlass
- stack: ReinforcedPlasmaGlass
+ stackEntity: SheetRPGlass
name: materials-reinforced-plasma-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rpglass }
color: "#8c4069"
diff --git a/Resources/Prototypes/Reagents/Materials/materials.yml b/Resources/Prototypes/Reagents/Materials/materials.yml
index c0144ad31d..3a3c86e10c 100644
--- a/Resources/Prototypes/Reagents/Materials/materials.yml
+++ b/Resources/Prototypes/Reagents/Materials/materials.yml
@@ -1,6 +1,6 @@
- type: material
id: Biomass
- stack: Biomass
+ stackEntity: MaterialBiomass
name: materials-biomass
icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube }
color: "#8A9A5B"
@@ -8,7 +8,7 @@
- type: material
id: Cloth
- stack: Cloth
+ stackEntity: MaterialCloth
name: materials-cloth
icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth }
color: "#e7e7de"
@@ -16,7 +16,7 @@
- type: material
id: Durathread
- stack: Durathread
+ stackEntity: MaterialDurathread
name: materials-durathread
icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread }
color: "#8291a1"
@@ -24,7 +24,7 @@
- type: material
id: Plasma
- stack: Plasma
+ stackEntity: SheetPlasma
name: materials-plasma
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plasma }
color: "#7e009e"
@@ -32,7 +32,7 @@
- type: material
id: Plastic
- stack: Plastic
+ stackEntity: SheetPlastic
name: materials-plastic
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plastic }
color: "#d9d9d9"
@@ -40,7 +40,7 @@
- type: material
id: Wood
- stack: WoodPlank
+ stackEntity: MaterialWoodPlank
name: materials-wood
icon: { sprite: Objects/Materials/materials.rsi, state: wood }
color: "#966F33"
@@ -48,7 +48,7 @@
- type: material
id: Uranium
- stack: Uranium
+ stackEntity: SheetUranium
name: materials-uranium
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: uranium }
color: "#32a852"
diff --git a/Resources/Prototypes/Reagents/Materials/metals.yml b/Resources/Prototypes/Reagents/Materials/metals.yml
index 4ef6f1c528..5baea2a86f 100644
--- a/Resources/Prototypes/Reagents/Materials/metals.yml
+++ b/Resources/Prototypes/Reagents/Materials/metals.yml
@@ -1,13 +1,13 @@
- type: material
id: Steel
- stack: Steel
+ stackEntity: SheetSteel
name: materials-steel
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: steel }
price: 0.05
- type: material
id: Gold
- stack: Gold
+ stackEntity: IngotGold
name: materials-gold
icon: { sprite: Objects/Materials/ingots.rsi, state: gold }
color: "#FFD700"
@@ -15,7 +15,7 @@
- type: material
id: Silver
- stack: Silver
+ stackEntity: IngotSilver
name: materials-silver
icon: { sprite: Objects/Materials/ingots.rsi, state: silver }
color: "#C0C0C0"
@@ -23,7 +23,7 @@
- type: material
id: Plasteel
- stack: Plasteel
+ stackEntity: SheetPlasteel
name: materials-plasteel
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: plasteel }
color: "#696969" #Okay, this is epic