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