Generalized material spawning (#12489)

This commit is contained in:
Rane
2023-01-07 13:09:05 -05:00
committed by GitHub
parent f1cb0ca37a
commit fecd60e98a
13 changed files with 177 additions and 60 deletions

View File

@@ -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;
/// <summary>
/// Materials and stacks have some odd relationships to entities,
/// so we need some test coverage for them.
/// </summary>
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<IMapManager>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var testMap = await PoolManager.CreateTestMap(pairTracker);
await server.WaitAssertion(() =>
{
var allMaterialProtos = prototypeManager.EnumeratePrototypes<MaterialPrototype>();
var coords = testMap.GridCoords;
foreach (var proto in allMaterialProtos)
{
if (proto.StackEntity == "")
continue;
var spawned = entityManager.SpawnEntity(proto.StackEntity, coords);
Assert.That(entityManager.HasComponent<StackComponent>(spawned),
$"{proto.ID} 'stack entity' {proto.StackEntity} has the stack component");
Assert.That(entityManager.HasComponent<MaterialComponent>(spawned),
$"{proto.ID} 'material stack' {proto.StackEntity} has the material component");
}
mapManager.DeleteMap(testMap.MapId);
});
}
}
}

View File

@@ -67,7 +67,6 @@ namespace Content.Server.Cloning
SubscribeLocalEvent<CloningPodComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<CloningPodComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<CloningPodComponent, RefreshPartsEvent>(OnPartsRefreshed); SubscribeLocalEvent<CloningPodComponent, RefreshPartsEvent>(OnPartsRefreshed);
SubscribeLocalEvent<CloningPodComponent, UpgradeExamineEvent>(OnUpgradeExamine); SubscribeLocalEvent<CloningPodComponent, UpgradeExamineEvent>(OnUpgradeExamine);
SubscribeLocalEvent<CloningPodComponent, MachineDeconstructedEvent>(OnDeconstruct);
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset); SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<BeingClonedComponent, MindAddedMessage>(HandleMindAdded); SubscribeLocalEvent<BeingClonedComponent, MindAddedMessage>(HandleMindAdded);
SubscribeLocalEvent<CloningPodComponent, PortDisconnectedEvent>(OnPortDisconnected); SubscribeLocalEvent<CloningPodComponent, PortDisconnectedEvent>(OnPortDisconnected);
@@ -96,11 +95,6 @@ namespace Content.Server.Cloning
args.AddPercentageUpgrade("cloning-pod-component-upgrade-biomass-requirement", component.BiomassRequirementMultiplier); 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) internal void TransferMindToClone(Mind.Mind mind)
{ {
if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) || if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) ||
@@ -322,8 +316,7 @@ namespace Content.Server.Cloning
} }
_spillableSystem.SpillAt(uid, bloodSolution, "PuddleBlood"); _spillableSystem.SpillAt(uid, bloodSolution, "PuddleBlood");
var biomassStack = Spawn(clonePod.MaterialCloningOutput, transform.Coordinates); _serverStackSystem.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
_stackSystem.SetCount(biomassStack, _robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)));
clonePod.UsedBiomass = 0; clonePod.UsedBiomass = 0;
RemCompDeferred<ActiveCloningPodComponent>(uid); RemCompDeferred<ActiveCloningPodComponent>(uid);

View File

@@ -33,12 +33,6 @@ namespace Content.Server.Cloning.Components
[DataField("requiredMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>)), ViewVariables(VVAccess.ReadWrite)] [DataField("requiredMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string RequiredMaterial = "Biomass"; public string RequiredMaterial = "Biomass";
/// <summary>
/// The entity that is spawned on machine deconstruct as well as failed cloning.
/// </summary>
[DataField("materialCloningOutput", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string MaterialCloningOutput = "MaterialBiomass";
/// <summary> /// <summary>
/// The base amount of time it takes to clone a body /// The base amount of time it takes to clone a body
/// </summary> /// </summary>

View File

@@ -1,9 +1,11 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Shared.Materials; using Content.Shared.Materials;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Server.Power.Components;
using Content.Shared.Database;
using Content.Shared.Stacks; 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; namespace Content.Server.Materials;
@@ -15,6 +17,21 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MaterialStorageComponent, MachineDeconstructedEvent>(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) public override bool TryInsertMaterialEntity(EntityUid user, EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? component = null)
{ {

View File

@@ -55,12 +55,6 @@ namespace Content.Server.Medical.BiomassReclaimer
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float YieldPerUnitMass = default; public float YieldPerUnitMass = default;
/// <summary>
/// The entity that is output by the reclaimer
/// </summary>
[DataField("outputEntityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string OutputEntityId = "MaterialBiomass";
/// <summary> /// <summary>
/// The base yield per mass unit when no components are upgraded. /// The base yield per mass unit when no components are upgraded.
/// </summary> /// </summary>

View File

@@ -76,7 +76,7 @@ namespace Content.Server.Medical.BiomassReclaimer
continue; 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.BloodReagent = null;
reclaimer.SpawnedEntities.Clear(); reclaimer.SpawnedEntities.Clear();

View File

@@ -1,9 +1,9 @@
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Materials;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Stack namespace Content.Server.Stack
@@ -16,6 +16,7 @@ namespace Content.Server.Stack
public sealed class StackSystem : SharedStackSystem public sealed class StackSystem : SharedStackSystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedStackSystem _sharedStack = default!;
public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 }; public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
@@ -87,24 +88,81 @@ namespace Content.Server.Stack
} }
/// <summary> /// <summary>
/// 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. /// This would spawn 3 stacks of 30 and 1 stack of 7.
/// </summary> /// </summary>
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition) public List<EntityUid> SpawnMultiple(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
{ {
var proto = _prototypeManager.Index<EntityPrototype>(entityPrototype); var list = new List<EntityUid>();
proto.TryGetComponent<StackComponent>(out var stack); if (amount <= 0)
var maxCountPerStack = GetMaxCount(stack); return list;
var spawnedEnts = new List<EntityUid>();
// 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<StackComponent>(firstSpawn, out var stack) || stack.StackTypeId == null)
return list;
if (!TryComp<MaterialComponent>(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) while (amount > 0)
{ {
var entity = Spawn(entityPrototype, spawnPosition); var entity = Spawn(materialProto.StackEntity, coordinates);
spawnedEnts.Add(entity); list.Add(entity);
var countAmount = Math.Min(maxCountPerStack, amount); var nextStack = Comp<StackComponent>(entity);
SetCount(entity, countAmount); if (amount > materialPerMaxCount)
amount -= countAmount; {
SetCount(entity, materialPerMaxCount, nextStack);
amount -= materialPerMaxCount;
} }
return spawnedEnts; else
{
SetCount(entity, (amount / materialPerStack), nextStack);
amount = 0;
}
}
return list;
}
/// <summary>
/// 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.
/// </summary>
public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
{
if (!_prototypeManager.TryIndex<MaterialPrototype>(material, out var stackType))
{
Logger.Error("Failed to index material prototype " + material);
return new List<EntityUid>();
}
return SpawnMultiple(amount, stackType, coordinates);
} }
private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args) private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args)

View File

@@ -200,7 +200,7 @@ public sealed partial class StoreSystem : EntitySystem
{ {
var cashId = proto.Cash[value]; var cashId = proto.Cash[value];
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / 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()); _hands.PickupOrDrop(buyer, ents.First());
amountRemaining -= value * amountToSpawn; amountRemaining -= value * amountToSpawn;
} }

View File

@@ -1,8 +1,7 @@
using Content.Shared.Stacks;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Utility;
namespace Content.Shared.Materials namespace Content.Shared.Materials
{ {
@@ -13,8 +12,6 @@ namespace Content.Shared.Materials
[Prototype("material")] [Prototype("material")]
public sealed class MaterialPrototype : IPrototype, IInheritingPrototype public sealed class MaterialPrototype : IPrototype, IInheritingPrototype
{ {
private string _name = string.Empty;
[ViewVariables] [ViewVariables]
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<MaterialPrototype>))] [ParentDataField(typeof(AbstractPrototypeIdArraySerializer<MaterialPrototype>))]
public string[]? Parents { get; } public string[]? Parents { get; }
@@ -27,11 +24,16 @@ namespace Content.Shared.Materials
[IdDataFieldAttribute] [IdDataFieldAttribute]
public string ID { get; } = default!; public string ID { get; } = default!;
[DataField("stack", customTypeSerializer:typeof(PrototypeIdSerializer<StackPrototype>))] /// <summary>
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.
/// </summary>
[DataField("stackEntity", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string StackEntity { get; } = "";
[DataField("name")] [DataField("name")]
public string Name { get; private set; } = ""; public string Name = "";
[DataField("color")] [DataField("color")]
public Color Color { get; } = Color.Gray; public Color Color { get; } = Color.Gray;

View File

@@ -44,7 +44,7 @@
- type: material - type: material
id: Credit id: Credit
name: spacebuck name: spacebuck
stack: Credit stackEntity: SpaceCash
icon: { sprite: /Textures/Objects/Economy/cash.rsi, state: cash } icon: { sprite: /Textures/Objects/Economy/cash.rsi, state: cash }
price: 1 price: 1

View File

@@ -1,6 +1,6 @@
- type: material - type: material
id: Glass id: Glass
stack: Glass stackEntity: SheetGlass
name: materials-glass name: materials-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: glass } icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: glass }
color: "#a8ccd7" color: "#a8ccd7"
@@ -8,7 +8,7 @@
- type: material - type: material
id: ReinforcedGlass id: ReinforcedGlass
stack: ReinforcedGlass stackEntity: SheetRGlass
name: materials-reinforced-glass name: materials-reinforced-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rglass } icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rglass }
color: "#549bb0" color: "#549bb0"
@@ -16,7 +16,7 @@
- type: material - type: material
id: PlasmaGlass id: PlasmaGlass
stack: PlasmaGlass stackEntity: SheetPGlass
name: materials-plasma-glass name: materials-plasma-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: pglass } icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: pglass }
color: "#b35989" color: "#b35989"
@@ -24,7 +24,7 @@
- type: material - type: material
id: ReinforcedPlasmaGlass id: ReinforcedPlasmaGlass
stack: ReinforcedPlasmaGlass stackEntity: SheetRPGlass
name: materials-reinforced-plasma-glass name: materials-reinforced-plasma-glass
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rpglass } icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rpglass }
color: "#8c4069" color: "#8c4069"

View File

@@ -1,6 +1,6 @@
- type: material - type: material
id: Biomass id: Biomass
stack: Biomass stackEntity: MaterialBiomass
name: materials-biomass name: materials-biomass
icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube } icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube }
color: "#8A9A5B" color: "#8A9A5B"
@@ -8,7 +8,7 @@
- type: material - type: material
id: Cloth id: Cloth
stack: Cloth stackEntity: MaterialCloth
name: materials-cloth name: materials-cloth
icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth } icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth }
color: "#e7e7de" color: "#e7e7de"
@@ -16,7 +16,7 @@
- type: material - type: material
id: Durathread id: Durathread
stack: Durathread stackEntity: MaterialDurathread
name: materials-durathread name: materials-durathread
icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread } icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread }
color: "#8291a1" color: "#8291a1"
@@ -24,7 +24,7 @@
- type: material - type: material
id: Plasma id: Plasma
stack: Plasma stackEntity: SheetPlasma
name: materials-plasma name: materials-plasma
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plasma } icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plasma }
color: "#7e009e" color: "#7e009e"
@@ -32,7 +32,7 @@
- type: material - type: material
id: Plastic id: Plastic
stack: Plastic stackEntity: SheetPlastic
name: materials-plastic name: materials-plastic
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plastic } icon: { sprite: Objects/Materials/Sheets/other.rsi, state: plastic }
color: "#d9d9d9" color: "#d9d9d9"
@@ -40,7 +40,7 @@
- type: material - type: material
id: Wood id: Wood
stack: WoodPlank stackEntity: MaterialWoodPlank
name: materials-wood name: materials-wood
icon: { sprite: Objects/Materials/materials.rsi, state: wood } icon: { sprite: Objects/Materials/materials.rsi, state: wood }
color: "#966F33" color: "#966F33"
@@ -48,7 +48,7 @@
- type: material - type: material
id: Uranium id: Uranium
stack: Uranium stackEntity: SheetUranium
name: materials-uranium name: materials-uranium
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: uranium } icon: { sprite: Objects/Materials/Sheets/other.rsi, state: uranium }
color: "#32a852" color: "#32a852"

View File

@@ -1,13 +1,13 @@
- type: material - type: material
id: Steel id: Steel
stack: Steel stackEntity: SheetSteel
name: materials-steel name: materials-steel
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: steel } icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: steel }
price: 0.05 price: 0.05
- type: material - type: material
id: Gold id: Gold
stack: Gold stackEntity: IngotGold
name: materials-gold name: materials-gold
icon: { sprite: Objects/Materials/ingots.rsi, state: gold } icon: { sprite: Objects/Materials/ingots.rsi, state: gold }
color: "#FFD700" color: "#FFD700"
@@ -15,7 +15,7 @@
- type: material - type: material
id: Silver id: Silver
stack: Silver stackEntity: IngotSilver
name: materials-silver name: materials-silver
icon: { sprite: Objects/Materials/ingots.rsi, state: silver } icon: { sprite: Objects/Materials/ingots.rsi, state: silver }
color: "#C0C0C0" color: "#C0C0C0"
@@ -23,7 +23,7 @@
- type: material - type: material
id: Plasteel id: Plasteel
stack: Plasteel stackEntity: SheetPlasteel
name: materials-plasteel name: materials-plasteel
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: plasteel } icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: plasteel }
color: "#696969" #Okay, this is epic color: "#696969" #Okay, this is epic