Generalized material spawning (#12489)
This commit is contained in:
59
Content.IntegrationTests/Tests/Materials/MaterialTests.cs
Normal file
59
Content.IntegrationTests/Tests/Materials/MaterialTests.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ namespace Content.Server.Cloning
|
||||
SubscribeLocalEvent<CloningPodComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<CloningPodComponent, RefreshPartsEvent>(OnPartsRefreshed);
|
||||
SubscribeLocalEvent<CloningPodComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||
SubscribeLocalEvent<CloningPodComponent, MachineDeconstructedEvent>(OnDeconstruct);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<BeingClonedComponent, MindAddedMessage>(HandleMindAdded);
|
||||
SubscribeLocalEvent<CloningPodComponent, PortDisconnectedEvent>(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<ActiveCloningPodComponent>(uid);
|
||||
|
||||
@@ -33,12 +33,6 @@ namespace Content.Server.Cloning.Components
|
||||
[DataField("requiredMaterial", customTypeSerializer: typeof(PrototypeIdSerializer<MaterialPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
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>
|
||||
/// The base amount of time it takes to clone a body
|
||||
/// </summary>
|
||||
|
||||
@@ -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<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)
|
||||
{
|
||||
|
||||
@@ -55,12 +55,6 @@ namespace Content.Server.Medical.BiomassReclaimer
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
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>
|
||||
/// The base yield per mass unit when no components are upgraded.
|
||||
/// </summary>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </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);
|
||||
proto.TryGetComponent<StackComponent>(out var stack);
|
||||
var maxCountPerStack = GetMaxCount(stack);
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
var list = new List<EntityUid>();
|
||||
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<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)
|
||||
{
|
||||
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<StackComponent>(entity);
|
||||
if (amount > materialPerMaxCount)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<MaterialPrototype>))]
|
||||
public string[]? Parents { get; }
|
||||
@@ -27,11 +24,16 @@ namespace Content.Shared.Materials
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("stack", customTypeSerializer:typeof(PrototypeIdSerializer<StackPrototype>))]
|
||||
public string? StackId { get; } = null;
|
||||
/// <summary>
|
||||
/// 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")]
|
||||
public string Name { get; private set; } = "";
|
||||
public string Name = "";
|
||||
|
||||
[DataField("color")]
|
||||
public Color Color { get; } = Color.Gray;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user