diff --git a/Content.Server/Botany/Components/AtmosphericGrowthComponent.cs b/Content.Server/Botany/Components/AtmosphericGrowthComponent.cs
new file mode 100644
index 00000000000..8894e3f6862
--- /dev/null
+++ b/Content.Server/Botany/Components/AtmosphericGrowthComponent.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Atmos;
+
+namespace Content.Server.Botany.Components;
+
+///
+/// Atmospheric-related requirements for proper entity growth. Used in botany.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class AtmosphericGrowthComponent : Component
+{
+ ///
+ /// Ideal temperature for plant growth in Kelvin.
+ ///
+ [DataField]
+ public float IdealHeat = Atmospherics.T20C;
+
+ ///
+ /// Temperature tolerance range around .
+ ///
+ [DataField]
+ public float HeatTolerance = 10f;
+
+ ///
+ /// Minimum pressure tolerance for plant growth.
+ ///
+ [DataField]
+ public float LowPressureTolerance = 81f;
+
+ ///
+ /// Maximum pressure tolerance for plant growth.
+ ///
+ [DataField]
+ public float HighPressureTolerance = 121f;
+}
diff --git a/Content.Server/Botany/Components/BasicGrowthComponent.cs b/Content.Server/Botany/Components/BasicGrowthComponent.cs
new file mode 100644
index 00000000000..aac178dfe7a
--- /dev/null
+++ b/Content.Server/Botany/Components/BasicGrowthComponent.cs
@@ -0,0 +1,21 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Basic parameters for plant growth.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class BasicGrowthComponent : Component
+{
+ ///
+ /// Amount of water consumed per growth tick.
+ ///
+ [DataField]
+ public float WaterConsumption = 0.5f;
+
+ ///
+ /// Amount of nutrients consumed per growth tick.
+ ///
+ [DataField]
+ public float NutrientConsumption = 0.75f;
+}
diff --git a/Content.Server/Botany/Components/BotanySwabComponent.cs b/Content.Server/Botany/Components/BotanySwabComponent.cs
index 5fad5764b41..8652dbcfa9f 100644
--- a/Content.Server/Botany/Components/BotanySwabComponent.cs
+++ b/Content.Server/Botany/Components/BotanySwabComponent.cs
@@ -1,19 +1,19 @@
-using System.Threading;
+namespace Content.Server.Botany.Components;
-namespace Content.Server.Botany
+///
+/// Anything that can be used to cross-pollinate plants.
+///
+[RegisterComponent]
+public sealed partial class BotanySwabComponent : Component
{
///
- /// Anything that can be used to cross-pollinate plants.
+ /// Delay between swab uses.
///
- [RegisterComponent]
- public sealed partial class BotanySwabComponent : Component
- {
- [DataField("swabDelay")]
- public float SwabDelay = 2f;
+ [DataField]
+ public TimeSpan SwabDelay = TimeSpan.FromSeconds(2);
- ///
- /// SeedData from the first plant that got swabbed.
- ///
- public SeedData? SeedData;
- }
+ ///
+ /// SeedData from the first plant that got swabbed.
+ ///
+ public SeedData? SeedData;
}
diff --git a/Content.Server/Botany/Components/ConsumeExudeGasGrowthComponent.cs b/Content.Server/Botany/Components/ConsumeExudeGasGrowthComponent.cs
new file mode 100644
index 00000000000..96d92693f30
--- /dev/null
+++ b/Content.Server/Botany/Components/ConsumeExudeGasGrowthComponent.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Atmos;
+
+namespace Content.Server.Botany.Components;
+
+///
+/// Data for gas to consume/exude on plant growth.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class ConsumeExudeGasGrowthComponent : Component
+{
+ ///
+ /// Dictionary of gases and their consumption rates per growth tick.
+ ///
+ [DataField] public Dictionary ConsumeGasses = new();
+
+ ///
+ /// Dictionary of gases and their exude rates per growth tick.
+ ///
+ [DataField] public Dictionary ExudeGasses = new();
+}
diff --git a/Content.Server/Botany/Components/GrowthComponentsHolder.cs b/Content.Server/Botany/Components/GrowthComponentsHolder.cs
new file mode 100644
index 00000000000..d2c8ef65fd8
--- /dev/null
+++ b/Content.Server/Botany/Components/GrowthComponentsHolder.cs
@@ -0,0 +1,87 @@
+using System.Linq;
+using System.Reflection;
+
+namespace Content.Server.Botany.Components;
+
+///
+/// TODO: Delete after plants transition to entities.
+/// This is an intentionally evil approach kept only to simplify the
+/// upcoming refactor: plants will become standalone entities that own these components.
+/// Once that happens, this holder is no longer needed.
+///
+[DataDefinition]
+public sealed partial class GrowthComponentsHolder
+{
+ public static readonly PropertyInfo[] ComponentGetters = typeof(GrowthComponentsHolder).GetProperties();
+ public static readonly Type[] GrowthComponentTypes = ComponentGetters.Select(x => x.PropertyType).ToArray();
+
+ ///
+ /// Extra-traits.
+ ///
+ [DataField]
+ public PlantTraitsComponent? PlantTraits { get; set; }
+
+ ///
+ /// Plant characteristics.
+ ///
+ [DataField]
+ public PlantComponent? Plant { get; set; }
+
+ ///
+ /// Basic properties for plant growth.
+ ///
+ [DataField]
+ public BasicGrowthComponent? BasicGrowth { get; set; }
+
+ ///
+ /// What defence plant have against toxins?
+ ///
+ [DataField]
+ public PlantToxinsComponent? Toxins { get; set; }
+
+ ///
+ /// Harvesting process-related data.
+ ///
+ [DataField]
+ public PlantHarvestComponent? Harvest { get; set; }
+
+ ///
+ /// Atmos-related environment requirements for plant growth.
+ ///
+ [DataField]
+ public AtmosphericGrowthComponent? AtmosphericGrowth { get; set; }
+
+ ///
+ /// What gases plant consume/exude upon growth.
+ ///
+ [DataField]
+ public ConsumeExudeGasGrowthComponent? ConsumeExudeGasGrowth { get; set; }
+
+ ///
+ /// Weeds and pests related data for plant.
+ ///
+ [DataField]
+ public WeedPestGrowthComponent? WeedPestGrowth { get; set; }
+
+ ///
+ /// Damage tolerance of plant.
+ ///
+ [DataField]
+ public UnviableGrowthComponent? UnviableGrowth { get; set; }
+
+ ///
+ /// Populates any null properties with default component instances so that
+ /// systems can always apply a full set. Existing (YAML-provided) values are kept.
+ ///
+ public void EnsureGrowthComponents()
+ {
+ foreach (var prop in ComponentGetters)
+ {
+ if (prop.GetValue(this) == null)
+ {
+ var instance = Activator.CreateInstance(prop.PropertyType); // this is really cursed and should not be used in master, also this should be blocked by sandboxing.
+ prop.SetValue(this, instance);
+ }
+ }
+ }
+}
diff --git a/Content.Server/Botany/Components/PlantComponent.cs b/Content.Server/Botany/Components/PlantComponent.cs
new file mode 100644
index 00000000000..066ec206da7
--- /dev/null
+++ b/Content.Server/Botany/Components/PlantComponent.cs
@@ -0,0 +1,51 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Component for storing core plant data.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class PlantComponent : Component
+{
+ ///
+ /// The plant's max health.
+ ///
+ [DataField]
+ public float Endurance = 100f;
+
+ ///
+ /// How many produce are created on harvest.
+ ///
+ [DataField]
+ public int Yield;
+
+ ///
+ /// The number of growth ticks this plant can be alive for. Plants take high damage levels when Age > Lifespan.
+ ///
+ [DataField]
+ public float Lifespan;
+
+ ///
+ /// The number of growth ticks it takes for a plant to reach its final growth stage.
+ ///
+ [DataField]
+ public float Maturation;
+
+ ///
+ /// The number of growth ticks it takes for a plant to be (re-)harvestable. Shouldn't be lower than Maturation.
+ ///
+ [DataField]
+ public float Production;
+
+ ///
+ /// How many different sprites appear before the plant is fully grown.
+ ///
+ [DataField]
+ public int GrowthStages = 6;
+
+ ///
+ /// A scalar for sprite size and chemical solution volume in the produce. Caps at 100.
+ ///
+ [DataField]
+ public float Potency = 1f;
+}
diff --git a/Content.Server/Botany/Components/PlantHarvestComponent.cs b/Content.Server/Botany/Components/PlantHarvestComponent.cs
new file mode 100644
index 00000000000..2a3c918218c
--- /dev/null
+++ b/Content.Server/Botany/Components/PlantHarvestComponent.cs
@@ -0,0 +1,48 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Data for plant harvesting process.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class PlantHarvestComponent : Component
+{
+ ///
+ /// Harvest repeat type.
+ ///
+ [DataField]
+ public HarvestType HarvestRepeat = HarvestType.NoRepeat;
+
+ ///
+ /// Whether the plant is currently ready for harvest.
+ ///
+ [ViewVariables]
+ public bool ReadyForHarvest = false;
+
+ ///
+ /// The age of the plant when last harvested.
+ ///
+ [ViewVariables]
+ public int LastHarvest = 0;
+}
+
+///
+/// Harvest options for plants.
+///
+public enum HarvestType
+{
+ ///
+ /// Plant is removed on harvest.
+ ///
+ NoRepeat,
+
+ ///
+ /// Plant makes produce every Production ticks.
+ ///
+ Repeat,
+
+ ///
+ /// Repeat, plus produce is dropped on the ground near the plant automatically.
+ ///
+ SelfHarvest
+}
diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs
index d65e1b702be..e72ec57d699 100644
--- a/Content.Server/Botany/Components/PlantHolderComponent.cs
+++ b/Content.Server/Botany/Components/PlantHolderComponent.cs
@@ -4,6 +4,9 @@ using Robust.Shared.Audio;
namespace Content.Server.Botany.Components;
+///
+/// Container for plant-holder and plant combined data.
+///
[RegisterComponent]
public sealed partial class PlantHolderComponent : Component
{
@@ -14,14 +17,8 @@ public sealed partial class PlantHolderComponent : Component
public TimeSpan NextUpdate = TimeSpan.Zero;
///
- /// Time between plant reagent consumption updates.
+ /// Number of missing gases required for plant growth.
///
- [DataField]
- public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
-
- [DataField]
- public int LastProduce;
-
[DataField]
public int MissingGas;
@@ -31,6 +28,12 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f);
+ ///
+ /// Time between plant reagent consumption updates.
+ ///
+ [DataField]
+ public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
+
///
/// Game time when the plant last did a growth update.
///
@@ -43,6 +46,9 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public SoundSpecifier? WateringSound;
+ ///
+ /// Whether to update the sprite after the next update cycle.
+ ///
[DataField]
public bool UpdateSpriteAfterUpdate;
@@ -53,33 +59,54 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public bool DrawWarnings = false;
+ ///
+ /// Current water level in the plant holder (0-100).
+ ///
[DataField]
public float WaterLevel = 100f;
+ ///
+ /// Current nutrient level in the plant holder (0-100).
+ ///
[DataField]
public float NutritionLevel = 100f;
+ ///
+ /// Current pest level in the plant holder (0-10).
+ ///
[DataField]
public float PestLevel;
+ ///
+ /// Current weed level in the plant holder (0-10).
+ ///
[DataField]
public float WeedLevel;
+ ///
+ /// Current toxin level in the plant holder (0-100).
+ ///
[DataField]
public float Toxins;
+ ///
+ /// Current age of the plant in growth cycles.
+ ///
[DataField]
public int Age;
+ ///
+ /// Number of growth cycles to skip due to poor conditions.
+ ///
[DataField]
public int SkipAging;
+ ///
+ /// Whether the plant is dead.
+ ///
[DataField]
public bool Dead;
- [DataField]
- public bool Harvest;
-
///
/// Set to true if this plant has been clipped by seed clippers. Used to prevent a single plant
/// from repeatedly being clipped.
@@ -93,18 +120,33 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public int YieldMod = 1;
+ ///
+ /// Multiplier for mutation chance and severity.
+ ///
[DataField]
public float MutationMod = 1f;
+ ///
+ /// Current mutation level (0-100).
+ ///
[DataField]
public float MutationLevel;
+ ///
+ /// Current health of the plant (0 to seed endurance).
+ ///
[DataField]
public float Health;
+ ///
+ /// Multiplier for weed growth rate.
+ ///
[DataField]
public float WeedCoefficient = 1f;
+ ///
+ /// Seed data for the currently planted seed.
+ ///
[DataField]
public SeedData? Seed;
@@ -120,12 +162,6 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public bool ImproperPressure;
- ///
- /// Not currently used.
- ///
- [DataField]
- public bool ImproperLight;
-
///
/// Set to true to force a plant update (visuals, component, etc.) regardless of the current
/// update cycle time. Typically used when some interaction affects this plant.
@@ -133,9 +169,15 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public bool ForceUpdate;
+ ///
+ /// Name of the solution container that holds the soil/nutrient solution.
+ ///
[DataField]
public string SoilSolutionName = "soil";
+ ///
+ /// Reference to the soil solution container.
+ ///
[ViewVariables]
public Entity? SoilSolution = null;
}
diff --git a/Content.Server/Botany/Components/PlantToxinsComponent.cs b/Content.Server/Botany/Components/PlantToxinsComponent.cs
new file mode 100644
index 00000000000..e02bdd196bc
--- /dev/null
+++ b/Content.Server/Botany/Components/PlantToxinsComponent.cs
@@ -0,0 +1,21 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Data for plant resistance to toxins.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class PlantToxinsComponent : Component
+{
+ ///
+ /// Maximum toxin level the plant can tolerate before taking damage.
+ ///
+ [DataField]
+ public float ToxinsTolerance = 4f;
+
+ ///
+ /// Divisor for calculating toxin uptake rate. Higher values mean slower toxin processing.
+ ///
+ [DataField]
+ public float ToxinUptakeDivisor = 10f;
+}
diff --git a/Content.Server/Botany/Components/PlantTraitsComponent.cs b/Content.Server/Botany/Components/PlantTraitsComponent.cs
new file mode 100644
index 00000000000..b2c1253060e
--- /dev/null
+++ b/Content.Server/Botany/Components/PlantTraitsComponent.cs
@@ -0,0 +1,49 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Botany.Components;
+
+///
+/// Component for managing special plant traits and mutations.
+///
+/// TODO: The logic for these component is quite hardcoded.
+/// They require a separate a system that will use events or APIs from other growth systems.
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class PlantTraitsComponent : Component
+{
+ ///
+ /// If true, produce can't be put into the seed maker.
+ ///
+ [DataField]
+ public bool Seedless = false;
+
+ ///
+ /// If true, a sharp tool is required to harvest this plant.
+ ///
+ [DataField]
+ public bool Ligneous = false;
+
+ ///
+ /// If true, the plant can scream when harvested.
+ ///
+ [DataField]
+ public bool CanScream = false;
+
+ ///
+ /// If true, the plant can turn into kudzu.
+ ///
+ [DataField]
+ public bool TurnIntoKudzu = false;
+
+ ///
+ /// Which kind of kudzu this plant will turn into if it kuzuifies.
+ ///
+ [DataField]
+ public EntProtoId KudzuPrototype = "WeakKudzu";
+
+ ///
+ /// If false, rapidly decrease health while growing. Adds a bit of challenge to keep mutated plants alive via Unviable's frequency.
+ ///
+ [DataField]
+ public bool Viable = true;
+}
diff --git a/Content.Server/Botany/Components/ProduceComponent.cs b/Content.Server/Botany/Components/ProduceComponent.cs
index db4ed62dd38..344fc7a89f6 100644
--- a/Content.Server/Botany/Components/ProduceComponent.cs
+++ b/Content.Server/Botany/Components/ProduceComponent.cs
@@ -1,24 +1,31 @@
using Content.Server.Botany.Systems;
using Content.Shared.Botany.Components;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Server.Botany.Components;
+///
+/// Produce-related data for plant and plant growth cycle.
+///
[RegisterComponent]
[Access(typeof(BotanySystem))]
public sealed partial class ProduceComponent : SharedProduceComponent
{
- [DataField("targetSolution")] public string SolutionName { get; set; } = "food";
+ ///
+ /// Name of the solution container that holds the produce's contents.
+ ///
+ [DataField("targetSolution")]
+ public string SolutionName { get; set; } = "food";
///
- /// Seed data used to create a when this produce has its seeds extracted.
+ /// Seed data used to create a when this produce has its seeds extracted.
///
[DataField]
public SeedData? Seed;
///
- /// Seed data used to create a when this produce has its seeds extracted.
+ /// Prototype ID for the seed that can be extracted from this produce.
///
- [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string? SeedId;
+ [DataField]
+ public ProtoId? SeedId;
}
diff --git a/Content.Server/Botany/Components/SeedComponent.cs b/Content.Server/Botany/Components/SeedComponent.cs
index ffa1b7ef4e9..0f82d88720a 100644
--- a/Content.Server/Botany/Components/SeedComponent.cs
+++ b/Content.Server/Botany/Components/SeedComponent.cs
@@ -1,30 +1,32 @@
using Content.Server.Botany.Systems;
using Content.Shared.Botany.Components;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
-namespace Content.Server.Botany.Components
+namespace Content.Server.Botany.Components;
+
+///
+/// Data container for plant seed. Contains all info (values for components) for new plant to grow from seed.
+///
+[RegisterComponent, Access(typeof(BotanySystem))]
+public sealed partial class SeedComponent : SharedSeedComponent
{
- [RegisterComponent, Access(typeof(BotanySystem))]
- public sealed partial class SeedComponent : SharedSeedComponent
- {
- ///
- /// Seed data containing information about the plant type & properties that this seed can grow seed. If
- /// null, will instead attempt to get data from a seed prototype, if one is defined. See .
- ///
- [DataField("seed")]
- public SeedData? Seed;
+ ///
+ /// Seed data containing information about the plant type & properties that this seed can grow seed. If
+ /// null, will instead attempt to get data from a seed prototype, if one is defined. See .
+ ///
+ [DataField]
+ public SeedData? Seed;
- ///
- /// If not null, overrides the plant's initial health. Otherwise, the plant's initial health is set to the Endurance value.
- ///
- [DataField]
- public float? HealthOverride = null;
+ ///
+ /// If not null, overrides the plant's initial health. Otherwise, the plant's initial health is set to the Endurance value.
+ ///
+ [DataField]
+ public float? HealthOverride = null;
- ///
- /// Name of a base seed prototype that is used if is null.
- ///
- [DataField("seedId", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string? SeedId;
- }
+ ///
+ /// Name of a base seed prototype that is used if is null.
+ ///
+ [DataField]
+ public ProtoId? SeedId;
}
diff --git a/Content.Server/Botany/Components/UnviableGrowthComponent.cs b/Content.Server/Botany/Components/UnviableGrowthComponent.cs
new file mode 100644
index 00000000000..745d8e1ab8e
--- /dev/null
+++ b/Content.Server/Botany/Components/UnviableGrowthComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Damage tolerance of plant.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class UnviableGrowthComponent : Component
+{
+ ///
+ /// Amount of damage dealt to the plant per growth tick with unviable.
+ ///
+ [DataField]
+ public float UnviableDamage = 6f;
+}
diff --git a/Content.Server/Botany/Components/WeedPestGrowthComponent.cs b/Content.Server/Botany/Components/WeedPestGrowthComponent.cs
new file mode 100644
index 00000000000..ae80a30c7ef
--- /dev/null
+++ b/Content.Server/Botany/Components/WeedPestGrowthComponent.cs
@@ -0,0 +1,52 @@
+namespace Content.Server.Botany.Components;
+
+///
+/// Data for weed and pest problems which can happen to plants - how well plant tolerates them,
+/// chances to develop them, how big of a problem they will be.
+///
+[RegisterComponent]
+[DataDefinition]
+public sealed partial class WeedPestGrowthComponent : Component
+{
+ ///
+ /// Maximum weed level the plant can tolerate before taking damage.
+ ///
+ [DataField]
+ public float WeedTolerance = 5f;
+
+ ///
+ /// Maximum pest level the plant can tolerate before taking damage.
+ ///
+ [DataField]
+ public float PestTolerance = 5f;
+
+ ///
+ /// Chance per tick for weeds to grow around this plant.
+ ///
+ [DataField]
+ public float WeedGrowthChance = 0.01f;
+
+ ///
+ /// Amount of weed growth per successful weed growth tick.
+ ///
+ [DataField]
+ public float WeedGrowthAmount = 0.5f;
+
+ ///
+ /// Weed level threshold at which the plant is considered overgrown and will transform into kudzu.
+ ///
+ [DataField]
+ public float WeedHighLevelThreshold = 10f;
+
+ ///
+ /// Chance per tick for pests to damage this plant.
+ ///
+ [DataField]
+ public float PestDamageChance = 0.05f;
+
+ ///
+ /// Amount of damage dealt to the plant per successful pest damage tick.
+ ///
+ [DataField]
+ public float PestDamageAmount = 1f;
+}
diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs
index a01d96a8781..8b58fdec0eb 100644
--- a/Content.Server/Botany/SeedPrototype.cs
+++ b/Content.Server/Botany/SeedPrototype.cs
@@ -1,10 +1,10 @@
-using Content.Shared.Atmos;
+using Content.Server.Botany.Components;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Random;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Serialization.Manager;
using Robust.Shared.Utility;
namespace Content.Server.Botany;
@@ -15,66 +15,29 @@ public sealed partial class SeedPrototype : SeedData, IPrototype
[IdDataField] public string ID { get; private set; } = default!;
}
-public enum HarvestType : byte
-{
- NoRepeat,
- Repeat,
- SelfHarvest
-}
-
-/*
- public enum PlantSpread : byte
- {
- NoSpread,
- Creepers,
- Vines,
- }
-
- public enum PlantMutation : byte
- {
- NoMutation,
- Mutable,
- HighlyMutable,
- }
-
- public enum PlantCarnivorous : byte
- {
- NotCarnivorous,
- EatPests,
- EatLivingBeings,
- }
-
- public enum PlantJuicy : byte
- {
- NotJuicy,
- Juicy,
- Slippery,
- }
-*/
-
[DataDefinition]
public partial struct SeedChemQuantity
{
///
/// Minimum amount of chemical that is added to produce, regardless of the potency
///
- [DataField("Min")] public FixedPoint2 Min = FixedPoint2.Epsilon;
+ [DataField] public FixedPoint2 Min = FixedPoint2.Epsilon;
///
/// Maximum amount of chemical that can be produced after taking plant potency into account.
///
- [DataField("Max")] public FixedPoint2 Max;
+ [DataField] public FixedPoint2 Max;
///
/// When chemicals are added to produce, the potency of the seed is divided with this value. Final chemical amount is the result plus the `Min` value.
/// Example: PotencyDivisor of 20 with seed potency of 55 results in 2.75, 55/20 = 2.75. If minimum is 1 then final result will be 3.75 of that chemical, 55/20+1 = 3.75.
///
- [DataField("PotencyDivisor")] public float PotencyDivisor;
+ [DataField] public float PotencyDivisor;
///
/// Inherent chemical is one that is NOT result of mutation or crossbreeding. These chemicals are removed if species mutation is executed.
///
- [DataField("Inherent")] public bool Inherent = true;
+ [DataField] public bool Inherent = true;
}
// TODO Make Botany ECS and give it a proper API. I removed the limited access of this class because it's egregious how many systems needed access to it due to a lack of an actual API.
@@ -89,34 +52,35 @@ public partial class SeedData
#region Tracking
///
- /// The name of this seed. Determines the name of seed packets.
+ /// The name of this seed. Determines the name of seed packets.
///
- [DataField("name")]
+ [DataField]
public string Name { get; private set; } = "";
///
- /// The noun for this type of seeds. E.g. for fungi this should probably be "spores" instead of "seeds". Also
- /// used to determine the name of seed packets.
+ /// The noun for this type of seeds. E.g. for fungi this should probably be "spores" instead of "seeds". Also
+ /// used to determine the name of seed packets.
///
- [DataField("noun")]
+ [DataField]
public string Noun { get; private set; } = "";
///
- /// Name displayed when examining the hydroponics tray. Describes the actual plant, not the seed itself.
+ /// Name displayed when examining the hydroponics tray. Describes the actual plant, not the seed itself.
///
- [DataField("displayName")]
+ [DataField]
public string DisplayName { get; private set; } = "";
- [DataField("mysterious")] public bool Mysterious;
+ [DataField] public bool Mysterious;
///
- /// If true, the properties of this seed cannot be modified.
+ /// If true, the properties of this seed cannot be modified.
///
- [DataField("immutable")] public bool Immutable;
+ [DataField]
+ public bool Immutable;
///
- /// If true, there is only a single reference to this seed and it's properties can be directly modified without
- /// needing to clone the seed.
+ /// If true, there is only a single reference to this seed and its properties can be directly modified without
+ /// needing to clone the seed.
///
[ViewVariables]
public bool Unique = false; // seed-prototypes or yaml-defined seeds for entity prototypes will not generally be unique.
@@ -124,91 +88,19 @@ public partial class SeedData
#region Output
///
- /// The entity prototype that is spawned when this type of seed is extracted from produce using a seed extractor.
- ///
- [DataField("packetPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string PacketPrototype = "SeedBase";
-
- ///
- /// The entity prototype this seed spawns when it gets harvested.
+ /// The entity prototype that is spawned when this type of seed is extracted from produce using a seed extractor.
///
[DataField]
- public List ProductPrototypes = new();
-
- [DataField] public Dictionary Chemicals = new();
-
- [DataField] public Dictionary ConsumeGasses = new();
-
- [DataField] public Dictionary ExudeGasses = new();
-
- #endregion
-
- #region Tolerances
-
- [DataField] public float NutrientConsumption = 0.75f;
-
- [DataField] public float WaterConsumption = 0.5f;
- [DataField] public float IdealHeat = 293f;
- [DataField] public float HeatTolerance = 10f;
- [DataField] public float IdealLight = 7f;
- [DataField] public float LightTolerance = 3f;
- [DataField] public float ToxinsTolerance = 4f;
-
- [DataField] public float LowPressureTolerance = 81f;
-
- [DataField] public float HighPressureTolerance = 121f;
-
- [DataField] public float PestTolerance = 5f;
-
- [DataField] public float WeedTolerance = 5f;
-
- [DataField] public float WeedHighLevelThreshold = 10f;
-
- #endregion
-
- #region General traits
-
- [DataField] public float Endurance = 100f;
-
- [DataField] public int Yield;
- [DataField] public float Lifespan;
- [DataField] public float Maturation;
- [DataField] public float Production;
- [DataField] public int GrowthStages = 6;
-
- [DataField] public HarvestType HarvestRepeat = HarvestType.NoRepeat;
-
- [DataField] public float Potency = 1f;
+ public EntProtoId PacketPrototype = "SeedBase";
///
- /// If true, cannot be harvested for seeds. Balances hybrids and
- /// mutations.
+ /// The entity prototypes that are spawned when this type of seed is harvested.
///
- [DataField] public bool Seedless = false;
+ [DataField]
+ public List ProductPrototypes = [];
- ///
- /// If false, rapidly decrease health while growing. Used to kill off
- /// plants with "bad" mutations.
- ///
- [DataField] public bool Viable = true;
-
- ///
- /// If true, a sharp tool is required to harvest this plant.
- ///
- [DataField] public bool Ligneous;
-
- // No, I'm not removing these.
- // if you re-add these, make sure that they get cloned.
- //public PlantSpread Spread { get; set; }
- //public PlantMutation Mutation { get; set; }
- //public float AlterTemperature { get; set; }
- //public PlantCarnivorous Carnivorous { get; set; }
- //public bool Parasite { get; set; }
- //public bool Hematophage { get; set; }
- //public bool Thorny { get; set; }
- //public bool Stinging { get; set; }
- // public bool Teleporting { get; set; }
- // public PlantJuicy Juicy { get; set; }
+ [DataField]
+ public Dictionary Chemicals = [];
#endregion
@@ -225,44 +117,51 @@ public partial class SeedData
[DataField]
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("PlantScreams", AudioParams.Default.WithVolume(-10));
- [DataField("screaming")] public bool CanScream;
-
- [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string KudzuPrototype = "WeakKudzu";
-
- [DataField] public bool TurnIntoKudzu;
- [DataField] public string? SplatPrototype { get; set; }
-
#endregion
///
/// The mutation effects that have been applied to this plant.
///
- [DataField] public List Mutations { get; set; } = new();
+ [DataField]
+ public List Mutations { get; set; } = [];
///
- /// The seed prototypes this seed may mutate into when prompted to.
+ /// The seed prototypes this seed may mutate into when prompted to.
///
[DataField]
- public List> MutationPrototypes = new();
+ public List> MutationPrototypes = [];
///
- /// Log impact for when the seed is planted.
+ /// The growth components used by this seed.
+ /// TODO: Delete after plants transition to entities
///
[DataField]
- public LogImpact? PlantLogImpact = null;
+ public GrowthComponentsHolder GrowthComponents = new();
///
- /// Log impact for when the seed is harvested.
+ /// Log impact for harvest operations.
///
[DataField]
- public LogImpact? HarvestLogImpact = null;
+ public LogImpact? HarvestLogImpact;
+
+ ///
+ /// Log impact for plant operations.
+ ///
+ [DataField]
+ public LogImpact? PlantLogImpact;
public SeedData Clone()
{
- DebugTools.Assert(!Immutable, "There should be no need to clone an immutable seed.");
+ if (Immutable)
+ return this;
+
+ var serializationManager = IoCManager.Resolve();
var newSeed = new SeedData
{
+ GrowthComponents = serializationManager.CreateCopy(GrowthComponents, notNullableOverride: true),
+ HarvestLogImpact = HarvestLogImpact,
+ PlantLogImpact = PlantLogImpact,
Name = Name,
Noun = Noun,
DisplayName = DisplayName,
@@ -272,40 +171,10 @@ public partial class SeedData
ProductPrototypes = new List(ProductPrototypes),
MutationPrototypes = new List>(MutationPrototypes),
Chemicals = new Dictionary(Chemicals),
- ConsumeGasses = new Dictionary(ConsumeGasses),
- ExudeGasses = new Dictionary(ExudeGasses),
-
- NutrientConsumption = NutrientConsumption,
- WaterConsumption = WaterConsumption,
- IdealHeat = IdealHeat,
- HeatTolerance = HeatTolerance,
- IdealLight = IdealLight,
- LightTolerance = LightTolerance,
- ToxinsTolerance = ToxinsTolerance,
- LowPressureTolerance = LowPressureTolerance,
- HighPressureTolerance = HighPressureTolerance,
- PestTolerance = PestTolerance,
- WeedTolerance = WeedTolerance,
-
- Endurance = Endurance,
- Yield = Yield,
- Lifespan = Lifespan,
- Maturation = Maturation,
- Production = Production,
- GrowthStages = GrowthStages,
- HarvestRepeat = HarvestRepeat,
- Potency = Potency,
-
- Seedless = Seedless,
- Viable = Viable,
- Ligneous = Ligneous,
PlantRsi = PlantRsi,
PlantIconState = PlantIconState,
- CanScream = CanScream,
- TurnIntoKudzu = TurnIntoKudzu,
- SplatPrototype = SplatPrototype,
- Mutations = new List(),
+ Mutations = new List(Mutations),
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
Unique = true,
@@ -321,8 +190,12 @@ public partial class SeedData
///
public SeedData SpeciesChange(SeedData other)
{
+ var serializationManager = IoCManager.Resolve();
var newSeed = new SeedData
{
+ GrowthComponents = serializationManager.CreateCopy(other.GrowthComponents, notNullableOverride: true),
+ HarvestLogImpact = other.HarvestLogImpact,
+ PlantLogImpact = other.PlantLogImpact,
Name = other.Name,
Noun = other.Noun,
DisplayName = other.DisplayName,
@@ -333,41 +206,11 @@ public partial class SeedData
MutationPrototypes = new List>(other.MutationPrototypes),
Chemicals = new Dictionary(Chemicals),
- ConsumeGasses = new Dictionary(ConsumeGasses),
- ExudeGasses = new Dictionary(ExudeGasses),
-
- NutrientConsumption = NutrientConsumption,
- WaterConsumption = WaterConsumption,
- IdealHeat = IdealHeat,
- HeatTolerance = HeatTolerance,
- IdealLight = IdealLight,
- LightTolerance = LightTolerance,
- ToxinsTolerance = ToxinsTolerance,
- LowPressureTolerance = LowPressureTolerance,
- HighPressureTolerance = HighPressureTolerance,
- PestTolerance = PestTolerance,
- WeedTolerance = WeedTolerance,
-
- Endurance = Endurance,
- Yield = Yield,
- Lifespan = Lifespan,
- Maturation = Maturation,
- Production = Production,
- GrowthStages = other.GrowthStages,
- HarvestRepeat = HarvestRepeat,
- Potency = Potency,
Mutations = Mutations,
- Seedless = Seedless,
- Viable = Viable,
- Ligneous = Ligneous,
-
PlantRsi = other.PlantRsi,
PlantIconState = other.PlantIconState,
- CanScream = CanScream,
- TurnIntoKudzu = TurnIntoKudzu,
- SplatPrototype = other.SplatPrototype,
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
Unique = true,
diff --git a/Content.Server/Botany/Systems/AtmosphericGrowthSystem.cs b/Content.Server/Botany/Systems/AtmosphericGrowthSystem.cs
new file mode 100644
index 00000000000..232f9fbe582
--- /dev/null
+++ b/Content.Server/Botany/Systems/AtmosphericGrowthSystem.cs
@@ -0,0 +1,55 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Botany.Components;
+using Content.Shared.Atmos;
+using Robust.Shared.Random;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Applies atmospheric temperature and pressure effects to plants during growth ticks.
+/// Uses current tile gas mixture to penalize or clear warnings based on tolerances.
+///
+public sealed class AtmosphericGrowthSystem : EntitySystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder))
+ return;
+
+ var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas;
+ if (MathF.Abs(environment.Temperature - component.IdealHeat) > component.HeatTolerance)
+ {
+ holder.Health -= _random.Next(1, 3);
+ holder.ImproperHeat = true;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ else
+ {
+ holder.ImproperHeat = false;
+ }
+
+ var pressure = environment.Pressure;
+ if (pressure < component.LowPressureTolerance || pressure > component.HighPressureTolerance)
+ {
+ holder.Health -= _random.Next(1, 3);
+ holder.ImproperPressure = true;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ else
+ {
+ holder.ImproperPressure = false;
+ }
+ }
+}
diff --git a/Content.Server/Botany/Systems/BasicGrowthSystem.cs b/Content.Server/Botany/Systems/BasicGrowthSystem.cs
new file mode 100644
index 00000000000..3ef0db6ce16
--- /dev/null
+++ b/Content.Server/Botany/Systems/BasicGrowthSystem.cs
@@ -0,0 +1,192 @@
+using Content.Server.Botany.Components;
+using Content.Shared.Swab;
+using Robust.Shared.Random;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Handles baseline plant progression each growth tick: aging, resource consumption,
+/// simple viability checks, and basic swab cross-pollination behavior.
+///
+public sealed class BasicGrowthSystem : EntitySystem
+{
+ [Dependency] private readonly BotanySystem _botany = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ // TODO: Multipliers should be taken from the hydroponics component.
+ ///
+ /// Multiplier for plant growth speed in hydroponics.
+ ///
+ public const float HydroponicsSpeedMultiplier = 1f;
+
+ ///
+ /// Multiplier for resource consumption (water, nutrients) in hydroponics.
+ ///
+ public const float HydroponicsConsumptionMultiplier = 2f;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ SubscribeLocalEvent(OnSwab);
+ }
+
+ private void OnSwab(Entity ent, ref BotanySwabDoAfterEvent args)
+ {
+ var component = ent.Comp;
+
+ if (args.Cancelled || args.Handled || args.Used == null)
+ return;
+
+ if (!TryComp(args.Used.Value, out var swab) || swab.SeedData == null)
+ return;
+
+ var swabComp = swab.SeedData.GrowthComponents.BasicGrowth;
+ if (swabComp == null)
+ {
+ swab.SeedData.GrowthComponents.BasicGrowth = new BasicGrowthComponent
+ {
+ WaterConsumption = component.WaterConsumption,
+ NutrientConsumption = component.NutrientConsumption
+ };
+ }
+ else
+ {
+ if (_random.Prob(0.5f))
+ swabComp.WaterConsumption = component.WaterConsumption;
+ if (_random.Prob(0.5f))
+ swabComp.NutrientConsumption = component.NutrientConsumption;
+ }
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder)
+ || !TryComp(uid, out var traits))
+ return;
+
+ if (holder.Seed == null || holder.Dead)
+ return;
+
+ // Check if the plant is viable.
+ if (!traits.Viable)
+ {
+ holder.Health -= _random.Next(5, 10) * HydroponicsSpeedMultiplier;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+
+ return;
+ }
+
+ // Advance plant age here.
+ if (holder.SkipAging > 0)
+ {
+ holder.SkipAging--;
+ }
+ else
+ {
+ if (_random.Prob(0.8f))
+ holder.Age += (int)(1 * HydroponicsSpeedMultiplier);
+
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+
+ if (holder.Age < 0) // Revert back to seed packet!
+ {
+ var packetSeed = holder.Seed;
+ // will put it in the trays hands if it has any, please do not try doing this.
+ _botany.SpawnSeedPacket(packetSeed, Transform(uid).Coordinates, uid);
+ _plantHolder.RemovePlant(uid, holder);
+ holder.ForceUpdate = true;
+ _plantHolder.Update(uid, holder);
+ return;
+ }
+
+ if (component.WaterConsumption > 0 && holder.WaterLevel > 0 && _random.Prob(0.75f))
+ {
+ holder.WaterLevel -= MathF.Max(0f,
+ component.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier);
+
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+
+ if (component.NutrientConsumption > 0 && holder.NutritionLevel > 0 && _random.Prob(0.75f))
+ {
+ holder.NutritionLevel -= MathF.Max(0f,
+ component.NutrientConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier);
+
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+
+ var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier;
+ if (holder.SkipAging < 10)
+ {
+ // Make sure the plant is not thirsty.
+ if (holder.WaterLevel > 10)
+ {
+ holder.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
+ }
+ else
+ {
+ AffectGrowth((uid, holder), -1);
+ holder.Health -= healthMod;
+ }
+
+ if (holder.NutritionLevel > 5)
+ {
+ holder.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
+ }
+ else
+ {
+ AffectGrowth((uid, holder), -1);
+ holder.Health -= healthMod;
+ }
+
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ }
+
+ ///
+ /// Affects the growth of a plant by modifying its age or production timing.
+ ///
+ public void AffectGrowth(Entity ent, int amount)
+ {
+ if (amount == 0)
+ return;
+
+ var (uid, component) = ent;
+
+ if (component.Seed == null)
+ return;
+
+ if (!TryComp(uid, out PlantHarvestComponent? harvest)
+ || !TryComp(uid, out PlantComponent? plant))
+ return;
+
+ if (amount > 0)
+ {
+ if (component.Age < plant.Maturation)
+ component.Age += amount;
+ else if (!harvest.ReadyForHarvest && plant.Yield <= 0f)
+ harvest.LastHarvest -= amount;
+ }
+ else
+ {
+ if (component.Age < plant.Maturation)
+ component.SkipAging++;
+ else if (!harvest.ReadyForHarvest && plant.Yield <= 0f)
+ harvest.LastHarvest += amount;
+ }
+ }
+}
+
+///
+/// Event of plant growing ticking.
+///
+[ByRefEvent]
+public readonly record struct OnPlantGrowEvent;
diff --git a/Content.Server/Botany/Systems/BotanySwabSystem.cs b/Content.Server/Botany/Systems/BotanySwabSystem.cs
index e8c7af92c27..d557d111de4 100644
--- a/Content.Server/Botany/Systems/BotanySwabSystem.cs
+++ b/Content.Server/Botany/Systems/BotanySwabSystem.cs
@@ -9,13 +9,14 @@ namespace Content.Server.Botany.Systems;
public sealed class BotanySwabSystem : EntitySystem
{
- [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly MutationSystem _mutationSystem = default!;
+ [Dependency] private readonly MutationSystem _mutation = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(OnExamined);
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
@@ -44,7 +45,7 @@ public sealed class BotanySwabSystem : EntitySystem
if (args.Target == null || !args.CanReach || !HasComp(args.Target))
return;
- _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, swab.SwabDelay, new BotanySwabDoAfterEvent(), uid, target: args.Target, used: uid)
+ _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, swab.SwabDelay, new BotanySwabDoAfterEvent(), uid, target: args.Target, used: uid)
{
Broadcast = true,
BreakOnMove = true,
@@ -62,18 +63,25 @@ public sealed class BotanySwabSystem : EntitySystem
if (swab.SeedData == null)
{
- // Pick up pollen
- swab.SeedData = plant.Seed;
- _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User);
+ // Pick up pollen.
+ if (plant.Seed != null)
+ swab.SeedData = plant.Seed.Clone();
+
+ _popup.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User);
}
else
{
var old = plant.Seed;
if (old == null)
return;
- plant.Seed = _mutationSystem.Cross(swab.SeedData, old); // Cross-pollenate
- swab.SeedData = old; // Transfer old plant pollen to swab
- _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Args.Target.Value, args.Args.User);
+
+ // Cross-pollinate the plants.
+ plant.Seed = _mutation.Cross(swab.SeedData, old);
+
+ // Transfer old plant pollen to swab.
+ swab.SeedData = old.Clone();
+
+ _popup.PopupEntity(Loc.GetString("botany-swab-to"), args.Args.Target.Value, args.Args.User);
}
args.Handled = true;
diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs
index bba3c48b867..c63ebdad378 100644
--- a/Content.Server/Botany/Systems/BotanySystem.Produce.cs
+++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs
@@ -11,7 +11,7 @@ public sealed partial class BotanySystem
public void ProduceGrown(EntityUid uid, ProduceComponent produce)
{
- if (!TryGetSeed(produce, out var seed))
+ if (!TryGetSeed(produce, out var seed) || !TryGetPlant(seed, out var plant))
return;
foreach (var mutation in seed.Mutations)
@@ -27,11 +27,12 @@ public sealed partial class BotanySystem
return;
solutionContainer.RemoveAllSolution();
+
foreach (var (chem, quantity) in seed.Chemicals)
{
var amount = quantity.Min;
- if (quantity.PotencyDivisor > 0 && seed.Potency > 0)
- amount += seed.Potency / quantity.PotencyDivisor;
+ if (quantity.PotencyDivisor > 0 && plant.Potency > 0)
+ amount += plant.Potency / quantity.PotencyDivisor;
amount = FixedPoint2.Clamp(amount, quantity.Min, quantity.Max);
solutionContainer.MaxVolume += amount;
solutionContainer.AddReagent(chem, amount);
diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs
index 6b26ce7119a..999966b4ac9 100644
--- a/Content.Server/Botany/Systems/BotanySystem.Seed.cs
+++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs
@@ -1,21 +1,19 @@
using Content.Server.Botany.Components;
using Content.Server.Popups;
-using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Administration.Logs;
using Content.Shared.Botany;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Kitchen.Components;
using Content.Shared.Popups;
using Content.Shared.Random;
-using Content.Shared.Random.Helpers;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Content.Shared.Administration.Logs;
-using Content.Shared.Database;
-using Content.Shared.Kitchen.Components;
namespace Content.Server.Botany.Systems;
@@ -48,9 +46,9 @@ public sealed partial class BotanySystem : EntitySystem
}
if (comp.SeedId != null
- && _prototypeManager.TryIndex(comp.SeedId, out SeedPrototype? protoSeed))
+ && _prototypeManager.TryIndex(comp.SeedId, out var protoSeed))
{
- seed = protoSeed;
+ seed = protoSeed.Clone();
return true;
}
@@ -67,7 +65,7 @@ public sealed partial class BotanySystem : EntitySystem
}
if (comp.SeedId != null
- && _prototypeManager.TryIndex(comp.SeedId, out SeedPrototype? protoSeed))
+ && _prototypeManager.TryIndex(comp.SeedId, out var protoSeed))
{
seed = protoSeed;
return true;
@@ -77,20 +75,34 @@ public sealed partial class BotanySystem : EntitySystem
return false;
}
+ /// TODO: Delete after plants transition to entities
+ public static bool TryGetPlant(SeedData? seed, [NotNullWhen(true)] out PlantComponent? plantComponent)
+ {
+ plantComponent = seed?.GrowthComponents.Plant;
+ return plantComponent != null;
+ }
+
+ /// TODO: Delete after plants transition to entities
+ public static bool TryGetPlantTraits(SeedData? seed, [NotNullWhen(true)] out PlantTraitsComponent? traitsComponent)
+ {
+ traitsComponent = seed?.GrowthComponents.PlantTraits;
+ return traitsComponent != null;
+ }
+
private void OnExamined(EntityUid uid, SeedComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
- if (!TryGetSeed(component, out var seed))
+ if (!TryGetSeed(component, out var seed) || !TryGetPlant(seed, out var plant))
return;
using (args.PushGroup(nameof(SeedComponent), 1))
{
var name = Loc.GetString(seed.DisplayName);
args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name)));
- args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield)));
- args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency)));
+ args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", plant.Yield)));
+ args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", plant.Potency)));
}
}
@@ -103,7 +115,7 @@ public sealed partial class BotanySystem : EntitySystem
{
var seed = Spawn(proto.PacketPrototype, coords);
var seedComp = EnsureComp(seed);
- seedComp.Seed = proto;
+ seedComp.Seed = proto.Clone();
seedComp.HealthOverride = healthOverride;
var name = Loc.GetString(proto.Name);
@@ -116,7 +128,7 @@ public sealed partial class BotanySystem : EntitySystem
return seed;
}
- public IEnumerable AutoHarvest(SeedData proto, EntityCoordinates position, int yieldMod = 1)
+ public IEnumerable AutoHarvest(SeedData proto, EntityCoordinates position, EntityUid plantEntity)
{
if (position.IsValid(EntityManager) &&
proto.ProductPrototypes.Count > 0)
@@ -124,18 +136,18 @@ public sealed partial class BotanySystem : EntitySystem
if (proto.HarvestLogImpact != null)
_adminLogger.Add(LogType.Botany, proto.HarvestLogImpact.Value, $"Auto-harvested {Loc.GetString(proto.Name):seed} at Pos:{position}.");
- return GenerateProduct(proto, position, yieldMod);
+ return GenerateProduct(proto, position, plantEntity);
}
- return Enumerable.Empty();
+ return [];
}
- public IEnumerable Harvest(SeedData proto, EntityUid user, int yieldMod = 1)
+ public IEnumerable Harvest(SeedData proto, EntityUid user, EntityUid plantEntity)
{
- if (proto.ProductPrototypes.Count == 0 || proto.Yield <= 0)
+ if (!TryGetPlant(proto, out var plant) || proto.ProductPrototypes.Count == 0 || plant.Yield <= 0)
{
_popupSystem.PopupCursor(Loc.GetString("botany-harvest-fail-message"), user, PopupType.Medium);
- return Enumerable.Empty();
+ return [];
}
var name = Loc.GetString(proto.DisplayName);
@@ -144,25 +156,32 @@ public sealed partial class BotanySystem : EntitySystem
if (proto.HarvestLogImpact != null)
_adminLogger.Add(LogType.Botany, proto.HarvestLogImpact.Value, $"{ToPrettyString(user):player} harvested {Loc.GetString(proto.Name):seed} at Pos:{Transform(user).Coordinates}.");
- return GenerateProduct(proto, Transform(user).Coordinates, yieldMod);
+ return GenerateProduct(proto, Transform(user).Coordinates, plantEntity);
}
- public IEnumerable GenerateProduct(SeedData proto, EntityCoordinates position, int yieldMod = 1)
+ public IEnumerable GenerateProduct(SeedData proto, EntityCoordinates position, EntityUid plantEntity)
{
+ if (!TryGetPlant(proto, out var plant))
+ return [];
+
+ var yieldMod = Comp(plantEntity).YieldMod;
+ var harvest = Comp(plantEntity);
+
var totalYield = 0;
- if (proto.Yield > -1)
+
+ if (plant.Yield > -1)
{
if (yieldMod < 0)
- totalYield = proto.Yield;
+ totalYield = plant.Yield;
else
- totalYield = proto.Yield * yieldMod;
+ totalYield = plant.Yield * yieldMod;
totalYield = Math.Max(1, totalYield);
}
var products = new List();
- if (totalYield > 1 || proto.HarvestRepeat != HarvestType.NoRepeat)
+ if (totalYield > 1 || harvest.HarvestRepeat != HarvestType.NoRepeat)
proto.Unique = false;
for (var i = 0; i < totalYield; i++)
@@ -175,10 +194,11 @@ public sealed partial class BotanySystem : EntitySystem
var produce = EnsureComp(entity);
- produce.Seed = proto;
+ produce.Seed = proto.Clone();
+
ProduceGrown(entity, produce);
- _appearance.SetData(entity, ProduceVisuals.Potency, proto.Potency);
+ _appearance.SetData(entity, ProduceVisuals.Potency, plant.Potency);
if (proto.Mysterious)
{
@@ -194,7 +214,10 @@ public sealed partial class BotanySystem : EntitySystem
public bool CanHarvest(SeedData proto, EntityUid? held = null)
{
- return !proto.Ligneous || proto.Ligneous && held != null && HasComp(held);
+ if (!TryGetPlantTraits(proto, out var traits))
+ return true;
+
+ return !traits.Ligneous || traits.Ligneous && held != null && HasComp(held);
}
#endregion
diff --git a/Content.Server/Botany/Systems/ConsumeExudeGasGrowthSystem.cs b/Content.Server/Botany/Systems/ConsumeExudeGasGrowthSystem.cs
new file mode 100644
index 00000000000..a98cf831504
--- /dev/null
+++ b/Content.Server/Botany/Systems/ConsumeExudeGasGrowthSystem.cs
@@ -0,0 +1,64 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Botany.Components;
+using Content.Shared.Atmos;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Consumes and emits configured gases around plants each growth tick, then merges
+/// the adjusted gas mixture back into the environment.
+///
+public sealed class ConsumeExudeGasGrowthSystem : EntitySystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder)
+ || !TryComp(uid, out PlantComponent? plant))
+ return;
+
+ var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas;
+
+ // Consume Gasses.
+ holder.MissingGas = 0;
+ if (component.ConsumeGasses.Count > 0)
+ {
+ foreach (var (gas, amount) in component.ConsumeGasses)
+ {
+ if (environment.GetMoles(gas) < amount)
+ {
+ holder.MissingGas++;
+ continue;
+ }
+
+ environment.AdjustMoles(gas, -amount);
+ }
+
+ if (holder.MissingGas > 0)
+ {
+ holder.Health -= holder.MissingGas * BasicGrowthSystem.HydroponicsSpeedMultiplier;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ }
+
+ // Exude Gasses.
+ var exudeCount = component.ExudeGasses.Count;
+ if (exudeCount > 0)
+ {
+ foreach (var (gas, amount) in component.ExudeGasses)
+ {
+ environment.AdjustMoles(gas,
+ MathF.Max(1f, MathF.Round(amount * MathF.Round(plant.Potency) / exudeCount)));
+ }
+ }
+ }
+}
diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs
index 834fd9e8efb..6bda05b0ad7 100644
--- a/Content.Server/Botany/Systems/MutationSystem.cs
+++ b/Content.Server/Botany/Systems/MutationSystem.cs
@@ -1,18 +1,21 @@
+using Content.Server.Botany.Components;
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
using Content.Shared.Random;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Linq;
+using Robust.Shared.Serialization.Manager;
-namespace Content.Server.Botany;
+namespace Content.Server.Botany.Systems;
public sealed class MutationSystem : EntitySystem
{
- private static ProtoId RandomPlantMutations = "RandomPlantMutations";
+ private static readonly ProtoId RandomPlantMutations = "RandomPlantMutations";
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
private RandomPlantMutationListPrototype _randomMutations = default!;
@@ -24,8 +27,6 @@ public sealed class MutationSystem : EntitySystem
///
/// For each random mutation, see if it occurs on this plant this check.
///
- ///
- ///
public void CheckRandomMutations(EntityUid plantHolder, ref SeedData seed, float severity)
{
foreach (var mutation in _randomMutations.mutations)
@@ -54,40 +55,43 @@ public sealed class MutationSystem : EntitySystem
}
CheckRandomMutations(plantHolder, ref seed, severity);
+ EnsureGrowthComponents(plantHolder, seed);
+ }
+
+ ///
+ /// Ensures that the plant has all the growth components specified in the seed data.
+ ///
+ private void EnsureGrowthComponents(EntityUid plantHolder, SeedData seed)
+ {
+ // Fill missing components in the seed with defaults.
+ seed.GrowthComponents.EnsureGrowthComponents();
+
+ foreach (var prop in GrowthComponentsHolder.ComponentGetters)
+ {
+ if (prop.GetValue(seed.GrowthComponents) is Component component && !EntityManager.HasComponent(plantHolder, component.GetType()))
+ {
+ var newComponent = _serializationManager.CreateCopy(component, notNullableOverride: true);
+ EntityManager.AddComponent(plantHolder, newComponent);
+ }
+ }
}
public SeedData Cross(SeedData a, SeedData b)
{
- SeedData result = b.Clone();
+ if (b.Immutable)
+ return b;
+
+ var result = b.Clone();
CrossChemicals(ref result.Chemicals, a.Chemicals);
- CrossFloat(ref result.NutrientConsumption, a.NutrientConsumption);
- CrossFloat(ref result.WaterConsumption, a.WaterConsumption);
- CrossFloat(ref result.IdealHeat, a.IdealHeat);
- CrossFloat(ref result.HeatTolerance, a.HeatTolerance);
- CrossFloat(ref result.IdealLight, a.IdealLight);
- CrossFloat(ref result.LightTolerance, a.LightTolerance);
- CrossFloat(ref result.ToxinsTolerance, a.ToxinsTolerance);
- CrossFloat(ref result.LowPressureTolerance, a.LowPressureTolerance);
- CrossFloat(ref result.HighPressureTolerance, a.HighPressureTolerance);
- CrossFloat(ref result.PestTolerance, a.PestTolerance);
- CrossFloat(ref result.WeedTolerance, a.WeedTolerance);
-
- CrossFloat(ref result.Endurance, a.Endurance);
- CrossInt(ref result.Yield, a.Yield);
- CrossFloat(ref result.Lifespan, a.Lifespan);
- CrossFloat(ref result.Maturation, a.Maturation);
- CrossFloat(ref result.Production, a.Production);
- CrossFloat(ref result.Potency, a.Potency);
-
- CrossBool(ref result.Seedless, a.Seedless);
- CrossBool(ref result.Ligneous, a.Ligneous);
- CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
- CrossBool(ref result.CanScream, a.CanScream);
-
- CrossGasses(ref result.ExudeGasses, a.ExudeGasses);
- CrossGasses(ref result.ConsumeGasses, a.ConsumeGasses);
+ if (BotanySystem.TryGetPlantTraits(a, out var sourceTraits) && BotanySystem.TryGetPlantTraits(result, out var resultTraits))
+ {
+ CrossBool(ref resultTraits.Seedless, sourceTraits.Seedless);
+ CrossBool(ref resultTraits.Ligneous, sourceTraits.Ligneous);
+ CrossBool(ref resultTraits.CanScream, sourceTraits.CanScream);
+ CrossBool(ref resultTraits.TurnIntoKudzu, sourceTraits.TurnIntoKudzu);
+ }
// LINQ Explanation
// For the list of mutation effects on both plants, use a 50% chance to pick each one.
@@ -98,7 +102,8 @@ public sealed class MutationSystem : EntitySystem
// effective hybrid crossings.
if (a.Name != result.Name && Random(0.7f))
{
- result.Seedless = true;
+ if (BotanySystem.TryGetPlantTraits(result, out var traits))
+ traits.Seedless = true;
}
return result;
@@ -110,9 +115,9 @@ public sealed class MutationSystem : EntitySystem
foreach (var otherChem in other)
{
// if both have same chemical, randomly pick potency ratio from the two.
- if (val.ContainsKey(otherChem.Key))
+ if (val.TryGetValue(otherChem.Key, out var value))
{
- val[otherChem.Key] = Random(0.5f) ? otherChem.Value : val[otherChem.Key];
+ val[otherChem.Key] = Random(0.5f) ? otherChem.Value : value;
}
// if target plant doesn't have this chemical, has 50% chance to add it.
else
@@ -148,9 +153,9 @@ public sealed class MutationSystem : EntitySystem
foreach (var otherGas in other)
{
// if both have same gas, randomly pick ammount from the two.
- if (val.ContainsKey(otherGas.Key))
+ if (val.TryGetValue(otherGas.Key, out var value))
{
- val[otherGas.Key] = Random(0.5f) ? otherGas.Value : val[otherGas.Key];
+ val[otherGas.Key] = Random(0.5f) ? otherGas.Value : value;
}
// if target plant doesn't have this gas, has 50% chance to add it.
else
diff --git a/Content.Server/Botany/Systems/PlantHarvestSystem.cs b/Content.Server/Botany/Systems/PlantHarvestSystem.cs
new file mode 100644
index 00000000000..d56914e869c
--- /dev/null
+++ b/Content.Server/Botany/Systems/PlantHarvestSystem.cs
@@ -0,0 +1,151 @@
+using Content.Server.Botany.Components;
+using Content.Server.Popups;
+using Content.Shared.Interaction;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Manages harvest readiness and execution for plants, including repeat/self-harvest
+/// logic and produce spawning, responding to growth and interaction events.
+///
+public sealed class HarvestSystem : EntitySystem
+{
+ [Dependency] private readonly BotanySystem _botany = default!;
+ [Dependency] private readonly PlantHolderSystem _holder = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ SubscribeLocalEvent(OnInteractHand);
+ SubscribeLocalEvent(OnInteractUsing);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder)
+ || !TryComp(uid, out PlantComponent? plant))
+ return;
+
+ if (component is { ReadyForHarvest: true, HarvestRepeat: HarvestType.SelfHarvest })
+ AutoHarvest((ent, ent, holder));
+
+ // Check if plant is ready for harvest.
+ var timeLastHarvest = holder.Age - component.LastHarvest;
+ if (timeLastHarvest > plant.Production && !component.ReadyForHarvest)
+ {
+ component.ReadyForHarvest = true;
+ component.LastHarvest = holder.Age;
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ }
+
+ private void OnInteractUsing(Entity ent, ref InteractUsingEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantTraitsComponent? traits)
+ || !traits.Ligneous
+ || !TryComp(uid, out PlantHolderComponent? holder)
+ || holder.Seed == null)
+ return;
+
+ if (!component.ReadyForHarvest || holder.Dead || holder.Seed == null)
+ return;
+
+ var canHarvestUsing = _botany.CanHarvest(holder.Seed, args.Used);
+ HandleInteraction((ent, ent, holder), args.User, !canHarvestUsing);
+ }
+
+ private void OnInteractHand(Entity ent, ref InteractHandEvent args)
+ {
+ if (!TryComp(ent, out PlantHolderComponent? holder)
+ || !TryComp(ent, out PlantTraitsComponent? traits))
+ return;
+
+ HandleInteraction((ent, ent, holder), args.User, traits.Ligneous);
+ }
+
+ private void HandleInteraction(
+ Entity ent,
+ EntityUid user,
+ bool missingRequiredTool
+ )
+ {
+ if (missingRequiredTool)
+ {
+ _popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user);
+ return;
+ }
+
+ var (_, harvest, holder) = ent;
+ if (!harvest.ReadyForHarvest || holder.Dead || holder.Seed == null)
+ return;
+
+ // Perform harvest.
+ DoHarvest(ent, user);
+ }
+
+ public void DoHarvest(Entity ent, EntityUid user)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder)
+ || !TryComp(uid, out PlantTraitsComponent? traits))
+ return;
+
+ if (holder.Dead)
+ {
+ // Remove dead plant.
+ _holder.RemovePlant(uid, holder);
+ AfterHarvest(ent);
+ return;
+ }
+
+ if (!component.ReadyForHarvest)
+ return;
+
+ // Spawn products.
+ if (holder.Seed != null)
+ _botany.Harvest(holder.Seed, user, ent);
+
+ // Handle harvest type.
+ if (component.HarvestRepeat == HarvestType.NoRepeat)
+ _holder.RemovePlant(uid, holder);
+
+ AfterHarvest(ent, holder, traits);
+ }
+
+ private void AfterHarvest(Entity ent, PlantHolderComponent? holder = null, PlantTraitsComponent? traits = null)
+ {
+ var (uid, component) = ent;
+ if (!Resolve(uid, ref traits, ref holder))
+ return;
+
+ component.ReadyForHarvest = false;
+ component.LastHarvest = holder.Age;
+
+ // Play scream sound if applicable.
+ if (traits.CanScream && holder.Seed != null)
+ _audio.PlayPvs(holder.Seed.ScreamSound, uid);
+
+ // Update sprite.
+ _holder.UpdateSprite(uid, holder);
+ }
+
+ ///
+ /// Auto-harvests a plant.
+ ///
+ public void AutoHarvest(Entity ent)
+ {
+ if (!ent.Comp1.ReadyForHarvest || ent.Comp2.Seed == null)
+ return;
+
+ _botany.AutoHarvest(ent.Comp2.Seed, Transform(ent.Owner).Coordinates, ent);
+ AfterHarvest(ent);
+ }
+}
diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs
index d5f331c157f..ba9782eef90 100644
--- a/Content.Server/Botany/Systems/PlantHolderSystem.cs
+++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs
@@ -1,19 +1,19 @@
-using Content.Server.Atmos.EntitySystems;
+using System.Linq;
using Content.Server.Botany.Components;
-using Content.Server.Hands.Systems;
using Content.Server.Popups;
using Content.Shared.Administration.Logs;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.Atmos;
using Content.Shared.Botany;
using Content.Shared.Burial.Components;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Coordinates.Helpers;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Database;
+using Content.Shared.EntityEffects;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
-using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
+using Content.Shared.Labels.Components;
using Content.Shared.Popups;
using Content.Shared.Random;
using Content.Shared.Tag;
@@ -22,47 +22,36 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
-using Content.Shared.Chemistry.Reaction;
-using Content.Shared.Containers.ItemSlots;
-using Content.Shared.Database;
-using Content.Shared.EntityEffects;
-using Content.Shared.Kitchen.Components;
-using Content.Shared.Labels.Components;
namespace Content.Server.Botany.Systems;
public sealed class PlantHolderSystem : EntitySystem
{
- [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
- [Dependency] private readonly BotanySystem _botany = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly MutationSystem _mutation = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly HandsSystem _hands = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly BotanySystem _botany = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly TagSystem _tagSystem = default!;
- [Dependency] private readonly RandomHelperSystem _randomHelper = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
+ [Dependency] private readonly ISerializationManager _copier = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
+ [Dependency] private readonly MutationSystem _mutation = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly RandomHelperSystem _randomHelper = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
-
- public const float HydroponicsSpeedMultiplier = 1f;
- public const float HydroponicsConsumptionMultiplier = 2f;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
private static readonly ProtoId HoeTag = "Hoe";
private static readonly ProtoId PlantSampleTakerTag = "PlantSampleTaker";
public override void Initialize()
{
- base.Initialize();
SubscribeLocalEvent(OnExamine);
SubscribeLocalEvent(OnInteractUsing);
- SubscribeLocalEvent(OnInteractHand);
SubscribeLocalEvent(OnSolutionTransferred);
}
@@ -75,6 +64,7 @@ public sealed class PlantHolderSystem : EntitySystem
{
if (plantHolder.NextUpdate > _gameTiming.CurTime)
continue;
+
plantHolder.NextUpdate = _gameTiming.CurTime + plantHolder.UpdateDelay;
Update(uid, plantHolder);
@@ -88,16 +78,24 @@ public sealed class PlantHolderSystem : EntitySystem
if (component.Seed == null)
return 0;
- var result = Math.Max(1, (int)(component.Age * component.Seed.GrowthStages / component.Seed.Maturation));
+ if (!TryComp(uid, out var plant))
+ return 0;
+
+ var result = Math.Max(1, (int)(component.Age * plant.GrowthStages / plant.Maturation));
return result;
}
private void OnExamine(Entity entity, ref ExaminedEvent args)
{
- if (!args.IsInDetailsRange)
+ var (uid, component) = entity;
+
+ PlantComponent? plant = null;
+ PlantTraitsComponent? traits = null;
+ if (!Resolve(uid, ref plant, ref traits))
return;
- var (_, component) = entity;
+ if (!args.IsInDetailsRange)
+ return;
using (args.PushGroup(nameof(PlantHolderComponent)))
{
@@ -112,28 +110,27 @@ public sealed class PlantHolderSystem : EntitySystem
("seedName", displayName),
("toBeForm", displayName.EndsWith('s') ? "are" : "is")));
- if (component.Health <= component.Seed.Endurance / 2)
+ if (component.Health <= plant.Endurance / 2)
{
args.PushMarkup(Loc.GetString(
"plant-holder-component-something-already-growing-low-health-message",
("healthState",
- Loc.GetString(component.Age > component.Seed.Lifespan
+ Loc.GetString(component.Age > plant.Lifespan
? "plant-holder-component-plant-old-adjective"
: "plant-holder-component-plant-unhealthy-adjective"))));
}
// For future reference, mutations should only appear on examine if they apply to a plant, not to produce.
-
- if (component.Seed.Ligneous)
+ if (traits.Ligneous)
args.PushMarkup(Loc.GetString("mutation-plant-ligneous"));
- if (component.Seed.TurnIntoKudzu)
+ if (traits.TurnIntoKudzu)
args.PushMarkup(Loc.GetString("mutation-plant-kudzu"));
- if (component.Seed.CanScream)
+ if (traits.CanScream)
args.PushMarkup(Loc.GetString("mutation-plant-scream"));
- if (component.Seed.Viable == false)
+ if (!traits.Viable)
args.PushMarkup(Loc.GetString("mutation-plant-unviable"));
}
else
@@ -157,9 +154,6 @@ public sealed class PlantHolderSystem : EntitySystem
if (component.Toxins > 40f)
args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning"));
- if (component.ImproperLight)
- args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning"));
-
if (component.ImproperHeat)
args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning"));
@@ -174,13 +168,13 @@ public sealed class PlantHolderSystem : EntitySystem
private void OnInteractUsing(Entity entity, ref InteractUsingEvent args)
{
- var (uid, component) = entity;
+ var (uid, plantHolder) = entity;
if (TryComp(args.Used, out SeedComponent? seeds))
{
- if (component.Seed == null)
+ if (plantHolder.Seed == null)
{
- if (!_botany.TryGetSeed(seeds, out var seed))
+ if (!_botany.TryGetSeed(seeds, out var seed) || !BotanySystem.TryGetPlant(seed, out var seedPlant))
return;
args.Handled = true;
@@ -188,20 +182,31 @@ public sealed class PlantHolderSystem : EntitySystem
var noun = Loc.GetString(seed.Noun);
_popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message",
("seedName", name),
- ("seedNoun", noun)), args.User, PopupType.Medium);
+ ("seedNoun", noun)),
+ args.User,
+ PopupType.Medium);
- component.Seed = seed;
- component.Dead = false;
- component.Age = 1;
- if (seeds.HealthOverride != null)
+ plantHolder.Seed = seed.Clone();
+ plantHolder.Dead = false;
+ plantHolder.Age = 1;
+
+ plantHolder.Health = seeds.HealthOverride ?? seedPlant.Endurance;
+
+ plantHolder.LastCycle = _gameTiming.CurTime;
+
+ // Ensure no existing growth components before adding new ones
+ RemoveAllGrowthComponents(uid);
+
+ // Fill missing components with defaults
+ seed.GrowthComponents.EnsureGrowthComponents();
+
+ foreach (var prop in GrowthComponentsHolder.ComponentGetters)
{
- component.Health = seeds.HealthOverride.Value;
+ if (prop.GetValue(seed.GrowthComponents) is Component growthComp)
+ {
+ EntityManager.AddComponent(uid, _copier.CreateCopy(growthComp, notNullableOverride: true), overwrite: true);
+ }
}
- else
- {
- component.Health = component.Seed.Endurance;
- }
- component.LastCycle = _gameTiming.CurTime;
if (TryComp(args.Used, out var paperLabel))
{
@@ -209,8 +214,7 @@ public sealed class PlantHolderSystem : EntitySystem
}
QueueDel(args.Used);
- CheckLevelSanity(uid, component);
- UpdateSprite(uid, component);
+ UpdateSprite(uid, plantHolder);
if (seed.PlantLogImpact != null)
_adminLogger.Add(LogType.Botany, seed.PlantLogImpact.Value, $"{ToPrettyString(args.User):player} planted {Loc.GetString(seed.Name):seed} at Pos:{Transform(uid).Coordinates}.");
@@ -219,22 +223,29 @@ public sealed class PlantHolderSystem : EntitySystem
}
args.Handled = true;
- _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message",
- ("name", Comp(uid).EntityName)), args.User, PopupType.Medium);
+ _popup.PopupCursor(
+ Loc.GetString("plant-holder-component-already-seeded-message", ("name", MetaData(uid).EntityName)),
+ args.User,
+ PopupType.Medium);
return;
}
- if (_tagSystem.HasTag(args.Used, HoeTag))
+ if (_tag.HasTag(args.Used, HoeTag))
{
args.Handled = true;
- if (component.WeedLevel > 0)
+ if (plantHolder.WeedLevel > 0)
{
- _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message",
- ("name", Comp(uid).EntityName)), args.User, PopupType.Medium);
- _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message",
- ("otherName", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true);
- component.WeedLevel = 0;
- UpdateSprite(uid, component);
+ _popup.PopupCursor(
+ Loc.GetString("plant-holder-component-remove-weeds-message", ("name", MetaData(uid).EntityName)),
+ args.User,
+ PopupType.Medium);
+ _popup.PopupEntity(
+ Loc.GetString("plant-holder-component-remove-weeds-others-message", ("otherName", MetaData(args.User).EntityName)),
+ uid,
+ Filter.PvsExcept(args.User),
+ true);
+ plantHolder.WeedLevel = 0;
+ UpdateSprite(uid, plantHolder);
}
else
{
@@ -247,39 +258,45 @@ public sealed class PlantHolderSystem : EntitySystem
if (HasComp(args.Used))
{
args.Handled = true;
- if (component.Seed != null)
+ if (plantHolder.Seed != null)
{
- _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message",
- ("name", Comp(uid).EntityName)), args.User, PopupType.Medium);
- _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message",
- ("name", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true);
- RemovePlant(uid, component);
+ _popup.PopupCursor(
+ Loc.GetString("plant-holder-component-remove-plant-message", ("name", MetaData(uid).EntityName)),
+ args.User,
+ PopupType.Medium);
+ _popup.PopupEntity(
+ Loc.GetString("plant-holder-component-remove-plant-others-message", ("name", MetaData(args.User).EntityName)),
+ uid,
+ Filter.PvsExcept(args.User),
+ true);
+ RemovePlant(uid, plantHolder);
}
else
{
- _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
- ("name", Comp(uid).EntityName)), args.User);
+ _popup.PopupCursor(
+ Loc.GetString("plant-holder-component-no-plant-message", ("name", MetaData(uid).EntityName)),
+ args.User);
}
return;
}
- if (_tagSystem.HasTag(args.Used, PlantSampleTakerTag))
+ if (_tag.HasTag(args.Used, PlantSampleTakerTag))
{
args.Handled = true;
- if (component.Seed == null)
+ if (plantHolder.Seed == null)
{
_popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User);
return;
}
- if (component.Sampled)
+ if (plantHolder.Sampled)
{
_popup.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User);
return;
}
- if (component.Dead)
+ if (plantHolder.Dead)
{
_popup.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User);
return;
@@ -291,40 +308,48 @@ public sealed class PlantHolderSystem : EntitySystem
return;
}
- component.Health -= (_random.Next(3, 5) * 10);
+ plantHolder.Health -= _random.Next(3, 5) * 10;
float? healthOverride;
- if (component.Harvest)
+ if (TryComp(uid, out var harvest) && harvest.ReadyForHarvest)
{
healthOverride = null;
}
else
{
- healthOverride = component.Health;
+ healthOverride = plantHolder.Health;
}
- var packetSeed = component.Seed;
- var seed = _botany.SpawnSeedPacket(packetSeed, Transform(args.User).Coordinates, args.User, healthOverride);
- _randomHelper.RandomOffset(seed, 0.25f);
- var displayName = Loc.GetString(component.Seed.DisplayName);
- _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
- ("seedName", displayName)), args.User);
- DoScream(entity.Owner, component.Seed);
+ var packetSeed = plantHolder.Seed;
+ if (packetSeed != null)
+ {
+ // Copy growth components from the plant to the seed before creating seed packet
+ var holder = new GrowthComponentsHolder();
- if (_random.Prob(0.3f))
- component.Sampled = true;
+ foreach (var prop in typeof(GrowthComponentsHolder).GetProperties())
+ {
+ if (EntityManager.TryGetComponent(uid, prop.PropertyType, out var growthComponent))
+ {
+ var copiedComponent = _copier.CreateCopy((Component)growthComponent, notNullableOverride: true);
+ prop.SetValue(holder, copiedComponent);
+ }
+ }
- // Just in case.
- CheckLevelSanity(uid, component);
- ForceUpdateByExternalCause(uid, component);
+ packetSeed.GrowthComponents = holder;
- return;
- }
+ var seed = _botany.SpawnSeedPacket(packetSeed, Transform(args.User).Coordinates, args.User, healthOverride);
+ _randomHelper.RandomOffset(seed, 0.25f);
+ var displayName = Loc.GetString(plantHolder.Seed.DisplayName);
+ _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
+ ("seedName", displayName)),
+ args.User);
+
+ if (_random.Prob(0.3f))
+ plantHolder.Sampled = true;
+
+ ForceUpdateByExternalCause(uid, plantHolder);
+ }
- if (HasComp(args.Used))
- {
- args.Handled = true;
- DoHarvest(uid, args.User, component);
return;
}
@@ -333,30 +358,35 @@ public sealed class PlantHolderSystem : EntitySystem
args.Handled = true;
_popup.PopupCursor(Loc.GetString("plant-holder-component-compost-message",
("owner", uid),
- ("usingItem", args.Used)), args.User, PopupType.Medium);
+ ("usingItem", args.Used)),
+ args.User,
+ PopupType.Medium);
_popup.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message",
("user", Identity.Entity(args.User, EntityManager)),
("usingItem", args.Used),
- ("owner", uid)), uid, Filter.PvsExcept(args.User), true);
+ ("owner", uid)),
+ uid,
+ Filter.PvsExcept(args.User),
+ true);
- if (_solutionContainerSystem.TryGetSolution(args.Used, produce.SolutionName, out var soln2, out var solution2))
+ if (_solutionContainer.TryGetSolution(args.Used, produce.SolutionName, out var soln2, out var solution2))
{
- if (_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution1))
+ if (_solutionContainer.ResolveSolution(uid, plantHolder.SoilSolutionName, ref plantHolder.SoilSolution, out var solution1))
{
// We try to fit as much of the composted plant's contained solution into the hydroponics tray as we can,
// since the plant will be consumed anyway.
var fillAmount = FixedPoint2.Min(solution2.Volume, solution1.AvailableVolume);
- _solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, _solutionContainerSystem.SplitSolution(soln2.Value, fillAmount));
+ _solutionContainer.TryAddSolution(plantHolder.SoilSolution.Value, _solutionContainer.SplitSolution(soln2.Value, fillAmount));
- ForceUpdateByExternalCause(uid, component);
+ ForceUpdateByExternalCause(uid, plantHolder);
}
}
var seed = produce.Seed;
- if (seed != null)
+ if (seed != null && BotanySystem.TryGetPlant(seed, out var seedPlant))
{
- var nutrientBonus = seed.Potency / 2.5f;
- AdjustNutrient(uid, nutrientBonus, component);
+ var nutrientBonus = seedPlant.Potency / 2.5f;
+ AdjustNutrient(uid, nutrientBonus, plantHolder);
}
QueueDel(args.Used);
}
@@ -366,16 +396,6 @@ public sealed class PlantHolderSystem : EntitySystem
{
_audio.PlayPvs(ent.Comp.WateringSound, ent.Owner);
}
- private void OnInteractHand(Entity entity, ref InteractHandEvent args)
- {
- DoHarvest(entity, args.User, entity.Comp);
- }
-
- public void WeedInvasion()
- {
- // TODO
- }
-
public void Update(EntityUid uid, PlantHolderComponent? component = null)
{
@@ -386,18 +406,26 @@ public sealed class PlantHolderSystem : EntitySystem
var curTime = _gameTiming.CurTime;
+ // ForceUpdate is used for external triggers like swabbing
if (component.ForceUpdate)
component.ForceUpdate = false;
- else if (curTime < (component.LastCycle + component.CycleDelay))
+ else if (curTime < component.LastCycle + component.CycleDelay)
{
if (component.UpdateSpriteAfterUpdate)
UpdateSprite(uid, component);
+
return;
}
component.LastCycle = curTime;
- // Process mutations
+ if (component is { Seed: not null, Dead: false })
+ {
+ var plantGrow = new OnPlantGrowEvent();
+ RaiseLocalEvent(uid, ref plantGrow);
+ }
+
+ // Process mutations. All plants can mutate, so this stays here.
if (component.MutationLevel > 0)
{
Mutate(uid, Math.Min(component.MutationLevel, 25), component);
@@ -405,40 +433,6 @@ public sealed class PlantHolderSystem : EntitySystem
component.MutationLevel = 0;
}
- // Weeds like water and nutrients! They may appear even if there's not a seed planted.
- if (component.WaterLevel > 10 && component.NutritionLevel > 5)
- {
- var chance = 0f;
- if (component.Seed == null)
- chance = 0.05f;
- else if (component.Seed.TurnIntoKudzu)
- chance = 1f;
- else
- chance = 0.01f;
-
- if (_random.Prob(chance))
- component.WeedLevel += 1 + HydroponicsSpeedMultiplier * component.WeedCoefficient;
-
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- if (component.Seed != null && component.Seed.TurnIntoKudzu
- && component.WeedLevel >= component.Seed.WeedHighLevelThreshold)
- {
- Spawn(component.Seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager));
- component.Seed.TurnIntoKudzu = false;
- component.Health = 0;
- }
-
- // There's a chance for a weed explosion to happen if weeds take over.
- // Plants that are themselves weeds (WeedTolerance > 8) are unaffected.
- if (component.WeedLevel >= 10 && _random.Prob(0.1f))
- {
- if (component.Seed == null || component.WeedLevel >= component.Seed.WeedTolerance + 2)
- WeedInvasion();
- }
-
// If we have no seed planted, or the plant is dead, stop processing here.
if (component.Seed == null || component.Dead)
{
@@ -448,337 +442,12 @@ public sealed class PlantHolderSystem : EntitySystem
return;
}
- // There's a small chance the pest population increases.
- // Can only happen when there's a live seed planted.
- if (_random.Prob(0.01f))
- {
- component.PestLevel += 0.5f * HydroponicsSpeedMultiplier;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- // Advance plant age here.
- if (component.SkipAging > 0)
- component.SkipAging--;
- else
- {
- if (_random.Prob(0.8f))
- component.Age += (int)(1 * HydroponicsSpeedMultiplier);
-
- component.UpdateSpriteAfterUpdate = true;
- }
-
- // Nutrient consumption.
- if (component.Seed.NutrientConsumption > 0 && component.NutritionLevel > 0 && _random.Prob(0.75f))
- {
- component.NutritionLevel -= MathF.Max(0f, component.Seed.NutrientConsumption * HydroponicsSpeedMultiplier);
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- // Water consumption.
- if (component.Seed.WaterConsumption > 0 && component.WaterLevel > 0 && _random.Prob(0.75f))
- {
- component.WaterLevel -= MathF.Max(0f,
- component.Seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier);
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier;
-
- // Make sure genetics are viable.
- if (!component.Seed.Viable)
- {
- AffectGrowth(uid, -1, component);
- component.Health -= 6 * healthMod;
- }
-
- // Prevents the plant from aging when lacking resources.
- // Limits the effect on aging so that when resources are added, the plant starts growing in a reasonable amount of time.
- if (component.SkipAging < 10)
- {
- // Make sure the plant is not starving.
- if (component.NutritionLevel > 5)
- {
- component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
- }
- else
- {
- AffectGrowth(uid, -1, component);
- component.Health -= healthMod;
- }
-
- // Make sure the plant is not thirsty.
- if (component.WaterLevel > 10)
- {
- component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
- }
- else
- {
- AffectGrowth(uid, -1, component);
- component.Health -= healthMod;
- }
-
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas;
-
- component.MissingGas = 0;
- if (component.Seed.ConsumeGasses.Count > 0)
- {
- foreach (var (gas, amount) in component.Seed.ConsumeGasses)
- {
- if (environment.GetMoles(gas) < amount)
- {
- component.MissingGas++;
- continue;
- }
-
- environment.AdjustMoles(gas, -amount);
- }
-
- if (component.MissingGas > 0)
- {
- component.Health -= component.MissingGas * HydroponicsSpeedMultiplier;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
- }
-
- // SeedPrototype pressure resistance.
- var pressure = environment.Pressure;
- if (pressure < component.Seed.LowPressureTolerance || pressure > component.Seed.HighPressureTolerance)
- {
- component.Health -= healthMod;
- component.ImproperPressure = true;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
- else
- {
- component.ImproperPressure = false;
- }
-
- // SeedPrototype ideal temperature.
- if (MathF.Abs(environment.Temperature - component.Seed.IdealHeat) > component.Seed.HeatTolerance)
- {
- component.Health -= healthMod;
- component.ImproperHeat = true;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
- else
- {
- component.ImproperHeat = false;
- }
-
- // Gas production.
- var exudeCount = component.Seed.ExudeGasses.Count;
- if (exudeCount > 0)
- {
- foreach (var (gas, amount) in component.Seed.ExudeGasses)
- {
- environment.AdjustMoles(gas,
- MathF.Max(1f, MathF.Round(amount * MathF.Round(component.Seed.Potency) / exudeCount)));
- }
- }
-
- // Toxin levels beyond the plant's tolerance cause damage.
- // They are, however, slowly reduced over time.
- if (component.Toxins > 0)
- {
- var toxinUptake = MathF.Max(1, MathF.Round(component.Toxins / 10f));
- if (component.Toxins > component.Seed.ToxinsTolerance)
- {
- component.Health -= toxinUptake;
- }
-
- component.Toxins -= toxinUptake;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- // Weed levels.
- if (component.PestLevel > 0)
- {
- // TODO: Carnivorous plants?
- if (component.PestLevel > component.Seed.PestTolerance)
- {
- component.Health -= HydroponicsSpeedMultiplier;
- }
-
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- // Weed levels.
- if (component.WeedLevel > 0)
- {
- // TODO: Parasitic plants.
- if (component.WeedLevel >= component.Seed.WeedTolerance)
- {
- component.Health -= HydroponicsSpeedMultiplier;
- }
-
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
-
- if (component.Age > component.Seed.Lifespan)
- {
- component.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier;
- if (component.DrawWarnings)
- component.UpdateSpriteAfterUpdate = true;
- }
- else if (component.Age < 0) // Revert back to seed packet!
- {
- var packetSeed = component.Seed;
- // will put it in the trays hands if it has any, please do not try doing this
- _botany.SpawnSeedPacket(packetSeed, Transform(uid).Coordinates, uid);
- RemovePlant(uid, component);
- component.ForceUpdate = true;
- Update(uid, component);
- return;
- }
-
CheckHealth(uid, component);
- if (component.Harvest && component.Seed.HarvestRepeat == HarvestType.SelfHarvest)
- AutoHarvest(uid, component);
-
- // If enough time has passed since the plant was harvested, we're ready to harvest again!
- if (!component.Dead && component.Seed.ProductPrototypes.Count > 0)
- {
- if (component.Age > component.Seed.Production)
- {
- if (component.Age - component.LastProduce > component.Seed.Production && !component.Harvest)
- {
- component.Harvest = true;
- component.LastProduce = component.Age;
- }
- }
- else
- {
- if (component.Harvest)
- {
- component.Harvest = false;
- component.LastProduce = component.Age;
- }
- }
- }
-
- CheckLevelSanity(uid, component);
-
if (component.UpdateSpriteAfterUpdate)
UpdateSprite(uid, component);
}
- //TODO: kill this bullshit
- public void CheckLevelSanity(EntityUid uid, PlantHolderComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.Seed != null)
- component.Health = MathHelper.Clamp(component.Health, 0, component.Seed.Endurance);
- else
- {
- component.Health = 0f;
- component.Dead = false;
- }
-
- component.MutationLevel = MathHelper.Clamp(component.MutationLevel, 0f, 100f);
- component.NutritionLevel = MathHelper.Clamp(component.NutritionLevel, 0f, 100f);
- component.WaterLevel = MathHelper.Clamp(component.WaterLevel, 0f, 100f);
- component.PestLevel = MathHelper.Clamp(component.PestLevel, 0f, 10f);
- component.WeedLevel = MathHelper.Clamp(component.WeedLevel, 0f, 10f);
- component.Toxins = MathHelper.Clamp(component.Toxins, 0f, 100f);
- component.YieldMod = MathHelper.Clamp(component.YieldMod, 0, 2);
- component.MutationMod = MathHelper.Clamp(component.MutationMod, 0f, 3f);
- }
-
- public bool DoHarvest(EntityUid plantholder, EntityUid user, PlantHolderComponent? component = null)
- {
- if (!Resolve(plantholder, ref component))
- return false;
-
- if (component.Seed == null || Deleted(user))
- return false;
-
-
- if (component.Harvest && !component.Dead)
- {
- if (_hands.TryGetActiveItem(user, out var activeItem))
- {
- if (!_botany.CanHarvest(component.Seed, activeItem))
- {
- _popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user);
- return false;
- }
- }
- else if (!_botany.CanHarvest(component.Seed))
- {
- return false;
- }
-
- _botany.Harvest(component.Seed, user, component.YieldMod);
- AfterHarvest(plantholder, component);
- return true;
- }
-
- if (!component.Dead)
- return false;
-
- RemovePlant(plantholder, component);
- AfterHarvest(plantholder, component);
- return true;
- }
-
- ///
- /// Force do scream on PlantHolder (like plant is screaming) using seed's ScreamSound specifier (collection or soundPath)
- ///
- ///
- public bool DoScream(EntityUid plantholder, SeedData? seed = null)
- {
- if (seed == null || seed.CanScream == false)
- return false;
-
- _audio.PlayPvs(seed.ScreamSound, plantholder);
- return true;
- }
-
- public void AutoHarvest(EntityUid uid, PlantHolderComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.Seed == null || !component.Harvest)
- return;
-
- _botany.AutoHarvest(component.Seed, Transform(uid).Coordinates);
- AfterHarvest(uid, component);
- }
-
- private void AfterHarvest(EntityUid uid, PlantHolderComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.Harvest = false;
- component.LastProduce = component.Age;
-
- DoScream(uid, component.Seed);
-
- if (component.Seed?.HarvestRepeat == HarvestType.NoRepeat)
- RemovePlant(uid, component);
-
- CheckLevelSanity(uid, component);
- UpdateSprite(uid, component);
- }
-
public void CheckHealth(EntityUid uid, PlantHolderComponent? component = null)
{
if (!Resolve(uid, ref component))
@@ -790,69 +459,51 @@ public sealed class PlantHolderSystem : EntitySystem
}
}
- public void Die(EntityUid uid, PlantHolderComponent? component = null)
+ public void Die(EntityUid uid, PlantHolderComponent component)
{
- if (!Resolve(uid, ref component))
+ PlantHarvestComponent? harvest = null;
+ if (!Resolve(uid, ref harvest))
return;
component.Dead = true;
- component.Harvest = false;
+ harvest.ReadyForHarvest = false;
component.MutationLevel = 0;
component.YieldMod = 1;
component.MutationMod = 1;
- component.ImproperLight = false;
- component.ImproperHeat = false;
component.ImproperPressure = false;
- component.WeedLevel += 1 * HydroponicsSpeedMultiplier;
+ component.WeedLevel += 1 * BasicGrowthSystem.HydroponicsSpeedMultiplier;
component.PestLevel = 0;
+
UpdateSprite(uid, component);
}
public void RemovePlant(EntityUid uid, PlantHolderComponent? component = null)
{
- if (!Resolve(uid, ref component))
+ PlantHarvestComponent? harvest = null;
+ if (!Resolve(uid, ref component, ref harvest))
return;
+ if (component.Seed == null)
+ return;
+
+ // Remove all growth components before planting new seed
+ RemoveAllGrowthComponents(uid);
+
component.YieldMod = 1;
component.MutationMod = 1;
component.PestLevel = 0;
component.Seed = null;
component.Dead = false;
component.Age = 0;
- component.LastProduce = 0;
+ harvest.LastHarvest = 0;
component.Sampled = false;
- component.Harvest = false;
- component.ImproperLight = false;
+ harvest.ReadyForHarvest = false;
component.ImproperPressure = false;
component.ImproperHeat = false;
UpdateSprite(uid, component);
}
- public void AffectGrowth(EntityUid uid, int amount, PlantHolderComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.Seed == null)
- return;
-
- if (amount > 0)
- {
- if (component.Age < component.Seed.Maturation)
- component.Age += amount;
- else if (!component.Harvest && component.Seed.Yield <= 0f)
- component.LastProduce -= amount;
- }
- else
- {
- if (component.Age < component.Seed.Maturation)
- component.SkipAging++;
- else if (!component.Harvest && component.Seed.Yield <= 0f)
- component.LastProduce += amount;
- }
- }
-
public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? component = null)
{
if (!Resolve(uid, ref component))
@@ -880,7 +531,7 @@ public sealed class PlantHolderSystem : EntitySystem
if (!Resolve(uid, ref component))
return;
- if (!_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution))
+ if (!_solutionContainer.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution))
return;
if (solution.Volume > 0 && component.MutationLevel < 25)
@@ -888,13 +539,11 @@ public sealed class PlantHolderSystem : EntitySystem
foreach (var entry in component.SoilSolution.Value.Comp.Solution.Contents)
{
var reagentProto = _prototype.Index(entry.Reagent.Prototype);
- _entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray(), entry.Quantity.Float());
+ _entityEffects.ApplyEffects(uid, [.. reagentProto.PlantMetabolisms], entry.Quantity.Float());
}
- _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, FixedPoint2.New(1));
+ _solutionContainer.RemoveEachReagent(component.SoilSolution.Value, FixedPoint2.New(1));
}
-
- CheckLevelSanity(uid, component);
}
private void Mutate(EntityUid uid, float severity, PlantHolderComponent? component = null)
@@ -903,27 +552,34 @@ public sealed class PlantHolderSystem : EntitySystem
return;
if (component.Seed != null)
- {
- EnsureUniqueSeed(uid, component);
_mutation.MutateSeed(uid, ref component.Seed, severity);
- }
}
public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null)
{
- if (!Resolve(uid, ref component))
+ PlantHarvestComponent? harvest = null;
+ PlantComponent? plant = null;
+ Resolve(uid, ref harvest, ref plant, ref component, false);
+
+ if (!TryComp(uid, out var app)
+ || component == null)
return;
+
component.UpdateSpriteAfterUpdate = false;
- if (!TryComp(uid, out var app))
- return;
-
- if (component.Seed != null)
+ // If no seed, clear visuals regardless of traits.
+ if (component.Seed == null)
+ {
+ _appearance.SetData(uid, PlantHolderVisuals.PlantState, string.Empty, app);
+ _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app);
+ _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, false, app);
+ }
+ else if (harvest != null && plant != null)
{
if (component.DrawWarnings)
{
- _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= component.Seed.Endurance / 2f);
+ _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= plant.Endurance / 2f);
}
if (component.Dead)
@@ -931,53 +587,40 @@ public sealed class PlantHolderSystem : EntitySystem
_appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
_appearance.SetData(uid, PlantHolderVisuals.PlantState, "dead", app);
}
- else if (component.Harvest)
+ else if (harvest.ReadyForHarvest)
{
_appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
_appearance.SetData(uid, PlantHolderVisuals.PlantState, "harvest", app);
}
- else if (component.Age < component.Seed.Maturation)
+ else if (component.Age < plant.Maturation)
{
var growthStage = GetCurrentGrowthStage((uid, component));
_appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
_appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{growthStage}", app);
- component.LastProduce = component.Age;
+ harvest.LastHarvest = component.Age;
}
else
{
_appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app);
- _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{component.Seed.GrowthStages}", app);
+ _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{plant.GrowthStages}", app);
}
}
- else
- {
- _appearance.SetData(uid, PlantHolderVisuals.PlantState, "", app);
- _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app);
- }
if (!component.DrawWarnings)
return;
+ // TODO: dehardcode those alert levels.
+ // Not obvious where they go, as plant holder have alerts, sure, but some plants could have
+ // very different consumption rates so it would make sense to have different thresholds
_appearance.SetData(uid, PlantHolderVisuals.WaterLight, component.WaterLevel <= 15, app);
_appearance.SetData(uid, PlantHolderVisuals.NutritionLight, component.NutritionLevel <= 8, app);
- _appearance.SetData(uid, PlantHolderVisuals.AlertLight,
- component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat ||
- component.ImproperLight || component.ImproperPressure || component.MissingGas > 0, app);
- _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, component.Harvest, app);
- }
-
- ///
- /// Check if the currently contained seed is unique. If it is not, clone it so that we have a unique seed.
- /// Necessary to avoid modifying global seeds.
- ///
- public void EnsureUniqueSeed(EntityUid uid, PlantHolderComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (component.Seed is { Unique: false })
- component.Seed = component.Seed.Clone();
+ _appearance.SetData(uid,
+ PlantHolderVisuals.AlertLight,
+ component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat
+ || component.ImproperPressure || component.MissingGas > 0,
+ app);
+ _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, harvest is { ReadyForHarvest: true }, app);
}
public void ForceUpdateByExternalCause(EntityUid uid, PlantHolderComponent? component = null)
@@ -989,4 +632,19 @@ public sealed class PlantHolderSystem : EntitySystem
component.ForceUpdate = true;
Update(uid, component);
}
+
+ ///
+ /// Removes all growth-related components from a plant.
+ /// TODO: Delete after plants transition to entities
+ ///
+ private void RemoveAllGrowthComponents(EntityUid uid)
+ {
+ foreach (var comp in EntityManager.GetComponents(uid))
+ {
+ if (GrowthComponentsHolder.GrowthComponentTypes.Contains(comp.GetType()))
+ {
+ RemComp(uid, comp);
+ }
+ }
+ }
}
diff --git a/Content.Server/Botany/Systems/PlantSystem.cs b/Content.Server/Botany/Systems/PlantSystem.cs
new file mode 100644
index 00000000000..23120c4a130
--- /dev/null
+++ b/Content.Server/Botany/Systems/PlantSystem.cs
@@ -0,0 +1,43 @@
+using Content.Server.Botany.Components;
+using Robust.Shared.Random;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Handles plant behavior and growth processing.
+///
+public sealed class PlantSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder))
+ return;
+
+ // Check if plant is too old.
+ if (holder.Age > component.Lifespan)
+ {
+ holder.Health -= _random.Next(3, 5) * BasicGrowthSystem.HydroponicsSpeedMultiplier;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+ }
+
+ ///
+ /// Adjusts the potency of a plant component.
+ ///
+ public void AdjustPotency(Entity ent, float delta)
+ {
+ var plant = ent.Comp;
+ plant.Potency = Math.Max(plant.Potency + delta, 1);
+ Dirty(ent);
+ }
+}
diff --git a/Content.Server/Botany/Systems/PlantToxinsSystem.cs b/Content.Server/Botany/Systems/PlantToxinsSystem.cs
new file mode 100644
index 00000000000..7c03da028fd
--- /dev/null
+++ b/Content.Server/Botany/Systems/PlantToxinsSystem.cs
@@ -0,0 +1,37 @@
+using Content.Server.Botany.Components;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Handles toxin accumulation and tolerance for plants, applying health damage
+/// and decrementing toxins based on per-tick uptake.
+///
+public sealed class ToxinsSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder ))
+ return;
+
+ if (holder.Toxins < 0)
+ return;
+
+ var toxinUptake = MathF.Max(1, MathF.Round(holder.Toxins / component.ToxinUptakeDivisor));
+ if (holder.Toxins > component.ToxinsTolerance)
+ holder.Health -= toxinUptake;
+
+ // there is a possibility that it will remove more toxin than amount of damage it took on plant health (and killed it).
+ // TODO: get min out of health left and toxin uptake - would work better, probably.
+ holder.Toxins -= toxinUptake;
+
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+}
diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs
index c7e20983a7a..fa030f50e30 100644
--- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs
+++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs
@@ -27,7 +27,11 @@ public sealed class SeedExtractorSystem : EntitySystem
if (!TryComp(args.Used, out ProduceComponent? produce))
return;
- if (!_botanySystem.TryGetSeed(produce, out var seed) || seed.Seedless)
+
+ if (!_botanySystem.TryGetSeed(produce, out var seed))
+ return;
+
+ if (!BotanySystem.TryGetPlantTraits(seed, out var traits) || traits.Seedless)
{
_popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-no-seeds", ("name", args.Used)),
args.User, PopupType.MediumCaution);
diff --git a/Content.Server/Botany/Systems/UnviableGrowthSystem.cs b/Content.Server/Botany/Systems/UnviableGrowthSystem.cs
new file mode 100644
index 00000000000..64ed3f32ad2
--- /dev/null
+++ b/Content.Server/Botany/Systems/UnviableGrowthSystem.cs
@@ -0,0 +1,28 @@
+using Content.Server.Botany.Components;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Applies a death chance and damage to unviable plants each growth tick, updating visuals when necessary.
+///
+public sealed class UnviableGrowthSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder)
+ || !BotanySystem.TryGetPlantTraits(holder.Seed, out var traits)
+ || traits.Viable)
+ return;
+
+ holder.Health -= component.UnviableDamage;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+}
diff --git a/Content.Server/Botany/Systems/WeedPestGrowthSystem.cs b/Content.Server/Botany/Systems/WeedPestGrowthSystem.cs
new file mode 100644
index 00000000000..0f0d8f00be4
--- /dev/null
+++ b/Content.Server/Botany/Systems/WeedPestGrowthSystem.cs
@@ -0,0 +1,80 @@
+using Content.Server.Botany.Components;
+using Content.Shared.Coordinates.Helpers;
+using Robust.Shared.Random;
+
+namespace Content.Server.Botany.Systems;
+
+///
+/// Manages weed growth and pest damage per growth tick, and handles tray-level
+/// weed spawning and kudzu transformation based on conditions.
+///
+public sealed class WeedPestGrowthSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPlantGrow);
+ SubscribeLocalEvent(OnTrayUpdate);
+ }
+
+ private void OnPlantGrow(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantHolderComponent? holder))
+ return;
+
+ // Weed growth logic.
+ if (_random.Prob(component.WeedGrowthChance))
+ {
+ holder.WeedLevel += component.WeedGrowthAmount;
+ if (holder.DrawWarnings)
+ holder.UpdateSpriteAfterUpdate = true;
+ }
+
+ // Pest damage logic.
+ if (_random.Prob(component.PestDamageChance))
+ holder.Health -= component.PestDamageAmount;
+ }
+
+ ///
+ /// Handles weed growth and kudzu transformation for plant holder trays.
+ ///
+ private void OnTrayUpdate(Entity ent, ref OnPlantGrowEvent args)
+ {
+ var (uid, component) = ent;
+
+ if (!TryComp(uid, out PlantTraitsComponent? traits)
+ || !TryComp(uid, out WeedPestGrowthComponent? weed))
+ return;
+
+ // Weeds like water and nutrients! They may appear even if there's not a seed planted.
+ if (component is { WaterLevel: > 10, NutritionLevel: > 5 })
+ {
+ float chance;
+ if (component.Seed == null)
+ chance = 0.05f;
+ else if (traits.TurnIntoKudzu)
+ chance = 1f;
+ else
+ chance = 0.01f;
+
+ if (_random.Prob(chance))
+ component.WeedLevel += 1 + component.WeedCoefficient * BasicGrowthSystem.HydroponicsSpeedMultiplier;
+
+ if (component.DrawWarnings)
+ component.UpdateSpriteAfterUpdate = true;
+ }
+
+ // Handle kudzu transformation.
+ if (component is { Seed: not null }
+ && traits.TurnIntoKudzu
+ && component.WeedLevel >= weed.WeedHighLevelThreshold)
+ {
+ Spawn(traits.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager));
+ traits.TurnIntoKudzu = false;
+ component.Health = 0;
+ }
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
index ebe5c83181c..2ac191ad413 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
@@ -5,15 +5,21 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that sets plant potency.
+///
public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem
{
- [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly PlantSystem _plant = default!;
+
protected override void Effect(Entity entity, ref EntityEffectEvent args)
{
if (entity.Comp.Seed == null || entity.Comp.Dead)
return;
- _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
- entity.Comp.Seed.Potency = Math.Max(entity.Comp.Seed.Potency + args.Effect.Amount, 1);
+ if (!TryComp(entity, out var plant))
+ return;
+
+ _plant.AdjustPotency((entity.Owner, plant), args.Effect.Amount);
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
index b0faa6255e1..571ff827b26 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
@@ -5,15 +5,18 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that increments plant age / growth cycle.
+///
public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem
{
- [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly BasicGrowthSystem _plantGrowth = default!;
protected override void Effect(Entity entity, ref EntityEffectEvent args)
{
if (entity.Comp.Seed == null || entity.Comp.Dead)
return;
- _plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity);
+ _plantGrowth.AffectGrowth(entity, (int)args.Effect.Amount);
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
index 3d82f74b116..f98a378be2c 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
@@ -19,37 +20,60 @@ public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSyst
if (entity.Comp.Seed == null || entity.Comp.Dead)
return;
- var effect = args.Effect;
- var member = entity.Comp.Seed.GetType().GetField(args.Effect.TargetValue);
+ var targetValue = args.Effect.TargetValue;
- if (member == null)
+ // Scan live plant growth components and mutate the first matching field.
+ foreach (var growthComp in EntityManager.GetComponents(entity.Owner))
{
- Log.Error($"{ effect.GetType().Name } Error: Member { args.Effect.TargetValue} not found on { entity.Comp.Seed.GetType().Name }. Did you misspell it?");
- return;
+
+ var componentType = growthComp.GetType();
+ if(!GrowthComponentsHolder.GrowthComponentTypes.Contains(componentType))
+ continue;
+
+ var field = componentType.GetField(targetValue);
+
+ if (field == null)
+ continue;
+
+ var currentValue = field.GetValue(growthComp);
+ if (currentValue == null)
+ continue;
+
+ if (TryGetValue(currentValue, out var floatVal))
+ {
+ MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
+ field.SetValue(growthComp, floatVal);
+ return;
+ }
+
+ if (TryGetValue(currentValue, out var intVal))
+ {
+ MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
+ field.SetValue(growthComp, intVal);
+ return;
+ }
+
+ if (TryGetValue(currentValue, out var boolVal))
+ {
+ field.SetValue(growthComp, !boolVal);
+ return;
+ }
}
- var currentValObj = member.GetValue(entity.Comp.Seed);
- if (currentValObj == null)
- return;
+ // Field not found in any component.
+ Log.Error($"{nameof(PlantChangeStat)} Error: Field '{targetValue}' not found in any plant component. Did you misspell it?");
+ }
- if (member.FieldType == typeof(float))
+ private bool TryGetValue(object value, out T? result)
+ {
+ result = default;
+ if (value is T val)
{
- var floatVal = (float)currentValObj;
- MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
- member.SetValue(entity.Comp.Seed, floatVal);
- }
- else if (member.FieldType == typeof(int))
- {
- var intVal = (int)currentValObj;
- MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
- member.SetValue(entity.Comp.Seed, intVal);
- }
- else if (member.FieldType == typeof(bool))
- {
- var boolVal = (bool)currentValObj;
- boolVal = !boolVal;
- member.SetValue(entity.Comp.Seed, boolVal);
+ result = val;
+ return true;
}
+
+ return false;
}
// Mutate reference 'val' between 'min' and 'max' by pretending the value
@@ -65,14 +89,14 @@ public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSyst
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
- int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+ var valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
// In other words, it tends to go to the middle.
- float probIncrease = 1 - (float)valInt / bits;
+ var probIncrease = 1 - (float)valInt / bits;
int valIntMutated;
if (_random.Prob(probIncrease))
{
@@ -84,7 +108,7 @@ public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSyst
}
// Set value based on mutated thermometer code.
- float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
+ var valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
val = valMutated;
}
@@ -98,14 +122,14 @@ public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSyst
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
- int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+ var valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
// In other words, it tends to go to the middle.
- float probIncrease = 1 - (float)valInt / bits;
+ var probIncrease = 1 - (float)valInt / bits;
int valMutated;
if (_random.Prob(probIncrease))
{
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
index 710bce24dde..c9d3bf7e221 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
@@ -5,6 +5,9 @@ using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that reverts aging of plant.
+///
public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
@@ -14,17 +17,16 @@ public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSyst
if (entity.Comp.Seed == null || entity.Comp.Dead)
return;
- var deviation = 0;
- var seed = entity.Comp.Seed;
- if (seed == null)
+ if (!TryComp(entity, out var plant) || !TryComp(entity, out var harvest))
return;
- if (entity.Comp.Age > seed.Maturation)
- deviation = (int) Math.Max(seed.Maturation - 1, entity.Comp.Age - _random.Next(7, 10));
- else
- deviation = (int) (seed.Maturation / seed.GrowthStages);
+
+ var deviation = entity.Comp.Age > plant.Maturation
+ ? (int)Math.Max(plant.Maturation - 1, entity.Comp.Age - _random.Next(7, 10))
+ : (int)(plant.Maturation / plant.GrowthStages);
+
entity.Comp.Age -= deviation;
- entity.Comp.LastProduce = entity.Comp.Age;
entity.Comp.SkipAging++;
entity.Comp.ForceUpdate = true;
+ harvest.LastHarvest = entity.Comp.Age;
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
index 1661c501be1..667e31cc684 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
@@ -7,6 +7,9 @@ using Content.Shared.Popups;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that removes ability to get seeds from plant using seed maker.
+///
public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
@@ -17,15 +20,14 @@ public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSy
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
return;
- if (entity.Comp.Seed.Seedless)
+ if (!TryComp(entity, out var traits) || traits.Seedless)
return;
- _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
_popup.PopupEntity(
Loc.GetString("botany-plant-seedsdestroyed"),
entity,
PopupType.SmallCaution
);
- entity.Comp.Seed.Seedless = true;
+ traits.Seedless = true;
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
index f6aebde465d..34575b2124c 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
@@ -1,31 +1,29 @@
using Content.Server.Botany.Components;
-using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that enhances plant longevity and endurance.
+///
public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
protected override void Effect(Entity entity, ref EntityEffectEvent args)
{
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
return;
- if (_random.Prob(0.1f))
- {
- _plantHolder.EnsureUniqueSeed(entity, entity);
- entity.Comp.Seed!.Lifespan++;
- }
+ if (!TryComp(entity, out var plant))
+ return;
if (_random.Prob(0.1f))
- {
- _plantHolder.EnsureUniqueSeed(entity, entity);
- entity.Comp.Seed!.Endurance++;
- }
+ plant.Lifespan++;
+
+ if (_random.Prob(0.1f))
+ plant.Endurance++;
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
index 8a073392e1c..fd28a1ea46f 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
@@ -4,6 +4,9 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that mutates plant to lose health with time.
+///
public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem
{
protected override void Effect(Entity entity, ref EntityEffectEvent args)
@@ -11,6 +14,7 @@ public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSy
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
return;
- entity.Comp.Seed.Viable = true;
+ if (TryComp(entity, out var traits))
+ traits.Viable = true;
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
index 4d724be2443..dc1081ca516 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
@@ -6,6 +6,9 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+///
+/// Entity effect that restores ability to get seeds from plant seed maker.
+///
public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
@@ -16,11 +19,10 @@ public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSy
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
return;
- if (!entity.Comp.Seed.Seedless)
+ if (!TryComp(entity, out var traits) || !traits.Seedless)
return;
- _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
_popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), entity);
- entity.Comp.Seed.Seedless = false;
+ traits.Seedless = false;
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
index 68ea3319ef4..79ed860343b 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
@@ -14,28 +14,26 @@ namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
protected override void Effect(Entity entity, ref EntityEffectEvent args)
{
if (entity.Comp.Seed == null || entity.Comp.Dead)
return;
- if (entity.Comp.Seed.Potency < args.Effect.PotencyLimit)
- {
- _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
- entity.Comp.Seed.Potency = Math.Min(entity.Comp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
+ if (!TryComp(entity, out var plant) || !TryComp(entity, out var traits))
+ return;
- if (entity.Comp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
- {
- entity.Comp.Seed.Seedless = true;
- }
- }
- else if (entity.Comp.Seed.Yield > 1 && _random.Prob(0.1f))
+ if (plant.Potency < args.Effect.PotencyLimit)
{
- // Too much of a good thing reduces yield
- _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
- entity.Comp.Seed.Yield--;
+ plant.Potency = Math.Min(plant.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
+
+ if (plant.Potency > args.Effect.PotencySeedlessThreshold)
+ traits.Seedless = true;
+ }
+ else if (plant.Yield > 1 && _random.Prob(0.1f))
+ {
+ // Too much of a good thing reduces yield.
+ plant.Yield--;
}
}
}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
index e2376ba186c..70f1f52bf39 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
@@ -1,4 +1,3 @@
-using System.Linq;
using Content.Server.Botany.Components;
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
@@ -7,6 +6,9 @@ using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.Botany;
+///
+/// Plant mutation entity effect that forces plant to exude gas while living.
+///
public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
@@ -16,11 +18,12 @@ public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffe
if (entity.Comp.Seed == null)
return;
- var gasses = entity.Comp.Seed.ExudeGasses;
+ var gasComponent = EnsureComp(entity);
+ var gasses = gasComponent.ExudeGasses;
- // Add a random amount of a random gas to this gas dictionary
+ // Add a random amount of a random gas to this gas dictionary.
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
- var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList());
+ var gas = _random.Pick(Enum.GetValues());
if (!gasses.TryAdd(gas, amount))
{
@@ -29,6 +32,9 @@ public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffe
}
}
+///
+/// Plant mutation entity effect that forces plant to consume gas while living.
+///
public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
@@ -38,11 +44,12 @@ public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEf
if (entity.Comp.Seed == null)
return;
- var gasses = entity.Comp.Seed.ConsumeGasses;
+ var gasComponent = EnsureComp(entity);
+ var gasses = gasComponent.ConsumeGasses;
- // Add a random amount of a random gas to this gas dictionary
+ // Add a random amount of a random gas to this gas dictionary.
var amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
- var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList());
+ var gas = _random.Pick(Enum.GetValues());
if (!gasses.TryAdd(gas, amount))
{
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
index 95d7f97bbe3..c96b80588c4 100644
--- a/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
@@ -1,10 +1,12 @@
-using Content.Server.Botany;
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects.Botany;
namespace Content.Server.EntityEffects.Effects.Botany;
+///
+/// Plant mutation entity effect that changes repeatability of plant harvesting (without re-planting).
+///
public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem
{
protected override void Effect(Entity entity, ref EntityEffectEvent args)
@@ -12,13 +14,14 @@ public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectS
if (entity.Comp.Seed == null)
return;
- switch (entity.Comp.Seed.HarvestRepeat)
+ var harvest = EnsureComp(entity);
+ switch (harvest.HarvestRepeat)
{
case HarvestType.NoRepeat:
- entity.Comp.Seed.HarvestRepeat = HarvestType.Repeat;
+ harvest.HarvestRepeat = HarvestType.Repeat;
break;
case HarvestType.Repeat:
- entity.Comp.Seed.HarvestRepeat = HarvestType.SelfHarvest;
+ harvest.HarvestRepeat = HarvestType.SelfHarvest;
break;
}
}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs
index c617c05b334..692bd0c400f 100644
--- a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs
+++ b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs
@@ -1,3 +1,5 @@
+using Robust.Shared.Prototypes;
+
namespace Content.Shared.EntityEffects.Effects.Botany;
///
@@ -10,6 +12,15 @@ public sealed partial class PlantMutateConsumeGases : EntityEffectBase
+ public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return Loc.GetString("entity-effect-guidebook-plant-mutate-consume-gasses",
+ ("chance", Probability),
+ ("minValue", MinValue),
+ ("maxValue", MaxValue));
+ }
}
public sealed partial class PlantMutateExudeGases : EntityEffectBase
@@ -19,4 +30,13 @@ public sealed partial class PlantMutateExudeGases : EntityEffectBase
+ public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return Loc.GetString("entity-effect-guidebook-plant-mutate-exude-gasses",
+ ("chance", Probability),
+ ("minValue", MinValue),
+ ("maxValue", MaxValue));
+ }
}
diff --git a/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl b/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
index fccc6291a8f..4acd2cdcfde 100644
--- a/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
+++ b/Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
@@ -513,6 +513,18 @@ entity-effect-guidebook-plant-seeds-remove =
*[other] remove the
} seeds of the plant
+entity-effect-guidebook-plant-mutate-exude-gasses =
+ { $chance ->
+ [1] Mutates
+ *[other] mutate
+ } the plant to exude gases between {$minValue} and {$maxValue} moles
+
+entity-effect-guidebook-plant-mutate-consume-gasses =
+ { $chance ->
+ [1] Mutates
+ *[other] mutate
+ } the plant to consume gases between {$minValue} and {$maxValue} moles
+
entity-effect-guidebook-plant-mutate-chemicals =
{ $chance ->
[1] Mutates
diff --git a/Resources/Maps/Salvage/vegan-meatball.yml b/Resources/Maps/Salvage/vegan-meatball.yml
index 824e3d0b7ea..f6b045d26a4 100644
--- a/Resources/Maps/Salvage/vegan-meatball.yml
+++ b/Resources/Maps/Salvage/vegan-meatball.yml
@@ -485,23 +485,13 @@ entities:
age: 1
health: 100
seed:
- splatPrototype: null
- lifespan: 75
- endurance: 100
- weedHighLevelThreshold: 10
- weedTolerance: 5
- pestTolerance: 5
- highPressureTolerance: 121
- lowPressureTolerance: 81
- toxinsTolerance: 4
- lightTolerance: 3
- idealLight: 9
- heatTolerance: 10
- idealHeat: 298
- waterConsumption: 0.4
- nutrientConsumption: 0.75
- exudeGasses: {}
- consumeGasses: {}
+ growthComponents:
+ plant:
+ lifespan: 75
+ basicGrowth:
+ waterConsumption: 0.4
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
THC:
Inherent: True
@@ -558,23 +548,13 @@ entities:
age: 1
health: 100
seed:
- splatPrototype: null
- lifespan: 25
- endurance: 100
- weedHighLevelThreshold: 10
- weedTolerance: 5
- pestTolerance: 5
- highPressureTolerance: 121
- lowPressureTolerance: 81
- toxinsTolerance: 4
- lightTolerance: 3
- idealLight: 7
- heatTolerance: 10
- idealHeat: 293
- waterConsumption: 0.6
- nutrientConsumption: 0.75
- exudeGasses: {}
- consumeGasses: {}
+ growthComponents:
+ plant:
+ lifespan: 25
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 293
chemicals:
Stellibinin:
Inherent: True
diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml
index 9a2b4eafae9..d90ff84e6d4 100644
--- a/Resources/Prototypes/Hydroponics/seeds.yml
+++ b/Resources/Prototypes/Hydroponics/seeds.yml
@@ -9,22 +9,24 @@
- WheatBushel
mutationPrototypes:
- meatwheat
- lifespan: 25
- maturation: 6
- production: 3
- yield: 3
- potency: 5
- idealLight: 8
- nutrientConsumption: 0.40
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 3
+ yield: 3
+ potency: 5
+ basicGrowth:
+ nutrientConsumption: 0.4
chemicals:
Nutriment:
- Min: 1
- Max: 20
- PotencyDivisor: 20
+ min: 1
+ max: 20
+ potencyDivisor: 20
Flour:
- Min: 5
- Max: 20
- PotencyDivisor: 20
+ min: 5
+ max: 20
+ potencyDivisor: 20
- type: seed
id: meatwheat
@@ -35,22 +37,24 @@
packetPrototype: MeatwheatSeeds
productPrototypes:
- MeatwheatBushel
- lifespan: 25
- maturation: 6
- production: 3
- yield: 3
- potency: 5
- idealLight: 8
- nutrientConsumption: 0.40
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 3
+ yield: 3
+ potency: 5
+ basicGrowth:
+ nutrientConsumption: 0.4
chemicals:
Nutriment:
- Min: 1
- Max: 20
- PotencyDivisor: 20
+ min: 1
+ max: 20
+ potencyDivisor: 20
UncookedAnimalProteins:
- Min: 5
- Max: 20
- PotencyDivisor: 20
+ min: 5
+ max: 20
+ potencyDivisor: 20
- type: seed
id: oat
@@ -61,22 +65,24 @@
packetPrototype: OatSeeds
productPrototypes:
- OatBushel
- lifespan: 25
- maturation: 6
- production: 3
- yield: 3
- potency: 5
- idealLight: 8
- nutrientConsumption: 0.40
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 3
+ yield: 3
+ potency: 5
+ basicGrowth:
+ nutrientConsumption: 0.4
chemicals:
Nutriment:
- Min: 1
- Max: 20
- PotencyDivisor: 20
+ min: 1
+ max: 20
+ potencyDivisor: 20
Oats:
- Min: 5
- Max: 20
- PotencyDivisor: 20
+ min: 5
+ max: 20
+ potencyDivisor: 20
- type: seed
id: banana
@@ -89,23 +95,27 @@
- FoodBanana
mutationPrototypes:
- mimana
- harvestRepeat: Repeat
- lifespan: 50
- maturation: 6
- production: 6
- yield: 2
- idealLight: 9
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 6
+ production: 6
+ yield: 2
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: mimana
@@ -116,23 +126,27 @@
packetPrototype: MimanaSeeds
productPrototypes:
- FoodMimana
- harvestRepeat: Repeat
- lifespan: 50
- maturation: 6
- production: 6
- yield: 2
- idealLight: 9
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 6
+ production: 6
+ yield: 2
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
MuteToxin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: carrots
@@ -143,26 +157,29 @@
packetPrototype: CarrotSeeds
productPrototypes:
- FoodCarrot
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
JuiceCarrot:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Oculine:
- Min: 2
- Max: 6
- PotencyDivisor: 20
+ min: 2
+ max: 6
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: laughinPea
@@ -175,29 +192,32 @@
- FoodLaughinPeaPod
mutationPrototypes:
- worldPea
- lifespan: 25
- growthStages: 3
- maturation: 7
- production: 5
- yield: 3
- potency: 20
- idealLight: 8
- harvestRepeat: Repeat
- nutrientConsumption: 0.6
- waterConsumption: 0.6
+ growthComponents:
+ plant:
+ lifespan: 25
+ growthStages: 3
+ maturation: 7
+ production: 5
+ yield: 3
+ potency: 20
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 3
- PotencyDivisor: 7
+ min: 1
+ max: 3
+ potencyDivisor: 7
Sugar:
- Min: 1
- Max: 10
- PotencyDivisor: 5
+ min: 1
+ max: 10
+ potencyDivisor: 5
Laughter:
- Min: 1
- Max: 10
- PotencyDivisor: 5
+ min: 1
+ max: 10
+ potencyDivisor: 5
- type: seed
id: lemon
@@ -212,22 +232,24 @@
- lemoon
- lime
- orange
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: lemoon
@@ -238,22 +260,24 @@
packetPrototype: LemoonSeeds
productPrototypes:
- FoodLemoon
- harvestRepeat: Repeat
- lifespan: 90
- maturation: 8
- production: 6
- yield: 4
- potency: 1
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 90
+ maturation: 8
+ production: 6
+ yield: 4
+ potency: 1
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Milk:
- Min: 8
- Max: 20
- PotencyDivisor: 5
+ min: 8
+ max: 20
+ potencyDivisor: 5
- type: seed
id: lime
@@ -267,22 +291,24 @@
mutationPrototypes:
- orange
- lemon
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: orange
@@ -297,22 +323,24 @@
- extradimensionalOrange
- lemon
- lime
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: extradimensionalOrange
@@ -323,26 +351,28 @@
packetPrototype: ExtradimensionalOrangeSeeds
productPrototypes:
- FoodExtradimensionalOrange
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Haloperidol:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: pineapple
@@ -353,27 +383,29 @@
packetPrototype: PineappleSeeds
productPrototypes:
- FoodPineapple
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 8
- growthStages: 3
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 20
- PotencyDivisor: 20
+ min: 1
+ max: 20
+ potencyDivisor: 20
Water:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Vitamin:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: potato
@@ -384,22 +416,25 @@
packetPrototype: PotatoSeeds
productPrototypes:
- FoodPotato
- lifespan: 30
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 4
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 30
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 4
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: sugarcane
@@ -412,19 +447,23 @@
- Sugarcane
mutationPrototypes:
- papercane
- harvestRepeat: Repeat
- lifespan: 60
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- growthStages: 3
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 60
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Sugar:
- Min: 4
- Max: 5
- PotencyDivisor: 5
+ min: 4
+ max: 5
+ potencyDivisor: 5
- type: seed
id: teaPlant
@@ -435,21 +474,25 @@
packetPrototype: TeaPlantSeeds
productPrototypes:
- LeavesTea
- harvestRepeat: Repeat
- lifespan: 75
- maturation: 5
- production: 3
- yield: 2
- potency: 20
- growthStages: 5
- waterConsumption: 0.6
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 75
+ maturation: 5
+ production: 3
+ yield: 2
+ potency: 20
+ growthStages: 5
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Vitamin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: papercane
@@ -460,14 +503,18 @@
packetPrototype: PapercaneSeeds
productPrototypes:
- Papercane
- harvestRepeat: Repeat
- lifespan: 60
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- growthStages: 3
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 60
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ atmosphericGrowth:
+ idealHeat: 298
- type: seed
id: towercap
@@ -480,17 +527,21 @@
- Log
mutationPrototypes:
- steelcap
- lifespan: 80
- maturation: 15
- ligneous: true
- production: 3
- yield: 5
- potency: 1
- growthStages: 3
- waterConsumption: 0.60
- nutrientConsumption: 0.50
- lightTolerance: 6
- idealHeat: 288
+ growthComponents:
+ plant:
+ lifespan: 80
+ maturation: 15
+ production: 3
+ yield: 5
+ potency: 1
+ growthStages: 3
+ plantTraits:
+ ligneous: true
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.5
+ atmosphericGrowth:
+ idealHeat: 288
- type: seed
id: steelcap
@@ -501,17 +552,21 @@
packetPrototype: SteelcapSeeds
productPrototypes:
- SteelLog
- lifespan: 80
- maturation: 15
- ligneous: true
- production: 3
- yield: 3
- potency: 1
- growthStages: 3
- waterConsumption: 0.60
- nutrientConsumption: 0.80
- lightTolerance: 6
- idealHeat: 288
+ growthComponents:
+ plant:
+ lifespan: 80
+ maturation: 15
+ production: 3
+ yield: 3
+ potency: 1
+ growthStages: 3
+ plantTraits:
+ ligneous: true
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.8
+ atmosphericGrowth:
+ idealHeat: 288
- type: seed
id: tomato
@@ -525,30 +580,33 @@
mutationPrototypes:
- blueTomato
- bloodTomato
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 10
- waterConsumption: 0.60
- nutrientConsumption: 0.40
- idealLight: 8
- idealHeat: 298
- splatPrototype: PuddleSplatter
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.4
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 7
- PotencyDivisor: 14
+ min: 1
+ max: 7
+ potencyDivisor: 14
Vitamin:
- Min: 1
- Max: 3
- PotencyDivisor: 33
+ min: 1
+ max: 3
+ potencyDivisor: 33
Water:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: blueTomato
@@ -559,30 +617,33 @@
packetPrototype: BlueTomatoSeeds
productPrototypes:
- FoodBlueTomato
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 10
- waterConsumption: 0.60
- nutrientConsumption: 0.70
- idealLight: 8
- idealHeat: 298
- splatPrototype: PuddleSplatter
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.7
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
SpaceLube:
- Min: 5
- Max: 15
- PotencyDivisor: 10
+ min: 5
+ max: 15
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: bloodTomato
@@ -595,26 +656,29 @@
- FoodBloodTomato
mutationPrototypes:
- killerTomato
- harvestRepeat: Repeat
- lifespan: 60
- maturation: 8
- production: 6
- yield: 2
- potency: 10
- waterConsumption: 0.60
- nutrientConsumption: 0.70
- idealLight: 8
- idealHeat: 298
- splatPrototype: PuddleSplatter
+ growthComponents:
+ plant:
+ lifespan: 60
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.7
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Blood:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: killerTomato
@@ -627,27 +691,30 @@
packetPrototype: KillerTomatoSeeds
productPrototypes:
- MobTomatoKiller
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 15
- production: 6
- yield: 2
- potency: 10
- waterConsumption: 0.60
- nutrientConsumption: 0.70
- idealLight: 8
- idealHeat: 298
- growthStages: 2
- splatPrototype: PuddleSplatter
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 15
+ production: 6
+ yield: 2
+ potency: 10
+ growthStages: 2
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.7
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Blood:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
JuiceTomato:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: eggplant
@@ -660,24 +727,27 @@
- FoodEggplant
mutationPrototypes:
- eggy
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 6
- production: 6
- yield: 2
- potency: 20
- growthStages: 3
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: cabbage
@@ -688,21 +758,23 @@
packetPrototype: CabbageSeeds
productPrototypes:
- FoodCabbage
- lifespan: 50
- maturation: 7
- production: 5
- yield: 3
- potency: 10
- growthStages: 1
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 7
+ production: 5
+ yield: 3
+ potency: 10
+ growthStages: 1
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: garlic
@@ -713,25 +785,27 @@
packetPrototype: GarlicSeeds
productPrototypes:
- FoodGarlic
- lifespan: 25
- maturation: 8
- production: 5
- yield: 3
- potency: 25
- growthStages: 3
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 5
+ yield: 3
+ potency: 25
+ growthStages: 3
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Allicin:
- Min: 1
- Max: 8
- PotencyDivisor: 25
+ min: 1
+ max: 8
+ potencyDivisor: 25
- type: seed
id: apple
@@ -744,22 +818,24 @@
- FoodApple
mutationPrototypes:
- goldenApple
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 6
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: goldenApple
@@ -770,28 +846,30 @@
packetPrototype: GoldenAppleSeeds
productPrototypes:
- FoodGoldenApple
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 3
- potency: 10
- idealLight: 6
- waterConsumption: 0.75
- nutrientConsumption: 0.75
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.75
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
DoctorsDelight:
- Min: 3
- Max: 13
- PotencyDivisor: 10
+ min: 3
+ max: 13
+ potencyDivisor: 10
- type: seed
id: corn
@@ -802,24 +880,27 @@
packetPrototype: CornSeeds
productPrototypes:
- FoodCorn
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 20
- growthStages: 3
- idealLight: 8
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 20
+ min: 1
+ max: 10
+ potencyDivisor: 20
Cornmeal:
- Min: 5
- Max: 15
- PotencyDivisor: 10
+ min: 5
+ max: 15
+ potencyDivisor: 10
- type: seed
id: onion
@@ -833,28 +914,31 @@
mutationPrototypes:
- onionred
- bloonion
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 20
- growthStages: 3
- idealLight: 8
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Allicin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: onionred
@@ -865,28 +949,31 @@
packetPrototype: OnionRedSeeds
productPrototypes:
- FoodOnionRed
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 20
- growthStages: 3
- idealLight: 8
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Allicin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: chanterelle
@@ -897,21 +984,24 @@
packetPrototype: ChanterelleSeeds
productPrototypes:
- FoodMushroom
- lifespan: 35
- maturation: 10
- production: 7
- yield: 5
- potency: 1
- growthStages: 3
- lightTolerance: 6
- waterConsumption: 0.60
- nutrientConsumption: 0.50
- idealHeat: 288
+ growthComponents:
+ plant:
+ lifespan: 35
+ maturation: 10
+ production: 7
+ yield: 5
+ potency: 1
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.5
+ atmosphericGrowth:
+ idealHeat: 288
chemicals:
Nutriment:
- Min: 1
- Max: 25
- PotencyDivisor: 25
+ min: 1
+ max: 25
+ potencyDivisor: 25
- type: seed
id: eggy
@@ -922,20 +1012,24 @@
packetPrototype: EggySeeds
productPrototypes:
- FoodEgg
- harvestRepeat: Repeat
- lifespan: 75
- maturation: 6
- production: 12
- yield: 2
- potency: 20
- nutrientConsumption: 0.50
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 75
+ maturation: 6
+ production: 12
+ yield: 2
+ potency: 20
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ nutrientConsumption: 0.5
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Egg:
- Min: 4
- Max: 12
- PotencyDivisor: 10
+ min: 4
+ max: 12
+ potencyDivisor: 10
- type: seed
id: cannabis
@@ -948,21 +1042,25 @@
- LeavesCannabis
mutationPrototypes:
- rainbowCannabis
- harvestRepeat: Repeat
- lifespan: 75
- maturation: 8
- production: 12
- yield: 2
- potency: 20
- growthStages: 3
- waterConsumption: 0.40
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 75
+ maturation: 8
+ production: 12
+ yield: 2
+ potency: 20
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.4
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
THC:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
- type: seed
id: rainbowCannabis
@@ -973,41 +1071,45 @@
packetPrototype: RainbowCannabisSeeds
productPrototypes:
- LeavesCannabisRainbow
- harvestRepeat: Repeat
- lifespan: 75
- maturation: 8
- production: 12
- yield: 2
- potency: 20
- growthStages: 3
- waterConsumption: 0.40
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 75
+ maturation: 8
+ production: 12
+ yield: 2
+ potency: 20
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.4
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
SpaceDrugs:
- Min: 1
- Max: 15
- PotencyDivisor: 10
+ min: 1
+ max: 15
+ potencyDivisor: 10
Lipolicide:
- Min: 1
- Max: 15
- PotencyDivisor: 10
+ min: 1
+ max: 15
+ potencyDivisor: 10
MindbreakerToxin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Happiness:
- Min: 1
- Max: 5
-# PotencyDivisor: 20
+ min: 1
+ max: 5
+# potencyDivisor: 20
# ColorfulReagent:
-# Min: 0
-# Max: 5
-# PotencyDivisor: 20
+# min: 0
+# max: 5
+# potencyDivisor: 20
Psicodine:
- Min: 0
- Max: 5
- PotencyDivisor: 33
+ min: 0
+ max: 5
+ potencyDivisor: 33
- type: seed
id: tobacco
@@ -1018,21 +1120,25 @@
packetPrototype: TobaccoSeeds
productPrototypes:
- LeavesTobacco
- harvestRepeat: Repeat
- lifespan: 75
- maturation: 5
- production: 5
- yield: 2
- potency: 20
- growthStages: 3
- waterConsumption: 0.40
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 75
+ maturation: 5
+ production: 5
+ yield: 2
+ potency: 20
+ growthStages: 3
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.4
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nicotine:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
- type: seed
id: nettle
@@ -1045,20 +1151,23 @@
- Nettle
mutationPrototypes:
- deathNettle
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 20
- growthStages: 5
- idealLight: 8
- waterConsumption: 0.60
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 5
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Histamine:
- Min: 1
- Max: 25
- PotencyDivisor: 4
+ min: 1
+ max: 25
+ potencyDivisor: 4
- type: seed
id: deathNettle
@@ -1071,25 +1180,28 @@
harvestLogImpact: High
productPrototypes:
- DeathNettle
- lifespan: 25
- maturation: 8
- production: 6
- yield: 2
- potency: 20
- growthStages: 5
- idealLight: 8
- waterConsumption: 0.70
- nutrientConsumption: 0.80
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 6
+ yield: 2
+ potency: 20
+ growthStages: 5
+ basicGrowth:
+ waterConsumption: 0.7
+ nutrientConsumption: 0.8
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
SulfuricAcid:
- Min: 1
- Max: 15
- PotencyDivisor: 6
+ min: 1
+ max: 15
+ potencyDivisor: 6
FluorosulfuricAcid:
- Min: 1
- Max: 15
- PotencyDivisor: 6
+ min: 1
+ max: 15
+ potencyDivisor: 6
- type: seed
id: chili
@@ -1102,27 +1214,30 @@
- FoodChiliPepper
mutationPrototypes:
- chilly
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 6
- production: 6
- yield: 2
- potency: 20
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 6
+ yield: 2
+ potency: 20
+ harvest:
+ harvestRepeat: Repeat
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
CapsaicinOil:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Nutriment:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: chilly
@@ -1133,27 +1248,30 @@
packetPrototype: ChillySeeds
productPrototypes:
- FoodChillyPepper
- harvestRepeat: Repeat
- lifespan: 25
- maturation: 6
- production: 6
- yield: 2
- potency: 20
- idealLight: 9
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 6
+ yield: 2
+ potency: 20
+ harvest:
+ harvestRepeat: Repeat
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
FrostOil:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Nutriment:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: poppy
@@ -1166,22 +1284,25 @@
- FoodPoppy
mutationPrototypes:
- lily
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
Bicaridine:
- Min: 1
- Max: 20
- PotencyDivisor: 5
+ min: 1
+ max: 20
+ potencyDivisor: 5
- type: seed
id: aloe
@@ -1192,22 +1313,25 @@
packetPrototype: AloeSeeds
productPrototypes:
- FoodAloe
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 5
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 5
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Aloe:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Dermaline:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
- type: seed
id: lily
@@ -1220,22 +1344,25 @@
- FoodLily
mutationPrototypes:
- spacemansTrumpet
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
Bicaridine:
- Min: 1
- Max: 20
- PotencyDivisor: 5
+ min: 1
+ max: 20
+ potencyDivisor: 5
- type: seed
id: lingzhi
@@ -1246,22 +1373,25 @@
packetPrototype: LingzhiSeeds
productPrototypes:
- FoodLingzhi
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Ultravasculine:
- Min: 1
- Max: 20
- PotencyDivisor: 5
+ min: 1
+ max: 20
+ potencyDivisor: 5
Epinephrine:
- Min: 1
- Max: 20
- PotencyDivisor: 5
+ min: 1
+ max: 20
+ potencyDivisor: 5
- type: seed
id: ambrosiaVulgaris
@@ -1274,30 +1404,33 @@
- FoodAmbrosiaVulgaris
mutationPrototypes:
- ambrosiaDeus
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 6
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 6
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 10
+ min: 1
+ max: 2
+ potencyDivisor: 10
Bicaridine:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Kelotane:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: ambrosiaDeus
@@ -1308,26 +1441,29 @@
packetPrototype: AmbrosiaDeusSeeds
productPrototypes:
- FoodAmbrosiaDeus
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 6
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 6
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 10
+ min: 1
+ max: 2
+ potencyDivisor: 10
Omnizine: # Don't kill me
- Min: 1
- Max: 3
- PotencyDivisor: 35
+ min: 1
+ max: 3
+ potencyDivisor: 35
SpaceDrugs:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: galaxythistle
@@ -1340,18 +1476,21 @@
- FoodGalaxythistle
mutationPrototypes:
- glasstle
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Stellibinin:
- Min: 1
- Max: 25
- PotencyDivisor: 4
+ min: 1
+ max: 25
+ potencyDivisor: 4
- type: seed
id: glasstle
@@ -1362,22 +1501,23 @@
packetPrototype: GlasstleSeeds
productPrototypes:
- FoodGlasstle
- lifespan: 25
- maturation: 10
- production: 3
- yield: 3
- potency: 10
- growthStages: 3
- waterConsumption: 0.5
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 10
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 3
chemicals:
Razorium:
- Min: 1
- Max: 25
- PotencyDivisor: 4
+ min: 1
+ max: 25
+ potencyDivisor: 4
Desoxyephedrine: # meff!!
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
- type: seed
id: flyAmanita
@@ -1388,23 +1528,26 @@
packetPrototype: FlyAmanitaSeeds
productPrototypes:
- FoodFlyAmanita
- lifespan: 25
- maturation: 12
- production: 3
- yield: 3
- potency: 10
- growthStages: 2
- waterConsumption: 0.60
- nutrientConsumption: 0.50
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 12
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 2
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.5
chemicals:
Amatoxin:
- Min: 1
- Max: 10
- PotencyDivisor: 12
+ min: 1
+ max: 10
+ potencyDivisor: 12
Nutriment: ## yumby :)
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: gatfruit
@@ -1418,22 +1561,23 @@
mutationPrototypes:
- fakeCapfruit
- realCapfruit
- lifespan: 65
- maturation: 25
- production: 25
- yield: 1
- potency: 10
- growthStages: 2
- idealLight: 6
+ growthComponents:
+ plant:
+ lifespan: 65
+ maturation: 25
+ production: 25
+ yield: 1
+ potency: 10
+ growthStages: 2
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Sulfur:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: fakeCapfruit
@@ -1444,22 +1588,23 @@
packetPrototype: FakeCapfruitSeeds
productPrototypes:
- FoodFakeCapfruit
- lifespan: 65
- maturation: 25
- production: 25
- yield: 1
- potency: 10
- growthStages: 2
- idealLight: 6
+ growthComponents:
+ plant:
+ lifespan: 65
+ maturation: 25
+ production: 25
+ yield: 1
+ potency: 10
+ growthStages: 2
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Sulfur:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: realCapfruit
@@ -1470,22 +1615,23 @@
packetPrototype: RealCapfruitSeeds
productPrototypes:
- FoodRealCapfruit
- lifespan: 65
- maturation: 25
- production: 25
- yield: 1
- potency: 10
- growthStages: 2
- idealLight: 6
+ growthComponents:
+ plant:
+ lifespan: 65
+ maturation: 25
+ production: 25
+ yield: 1
+ potency: 10
+ growthStages: 2
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Sulfur:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: rice
@@ -1496,24 +1642,26 @@
packetPrototype: RiceSeeds
productPrototypes:
- RiceBushel
- lifespan: 25
- maturation: 6
- production: 3
- yield: 3
- potency: 5
- growthStages: 4
- idealLight: 5
- nutrientConsumption: 0.40
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 6
+ production: 3
+ yield: 3
+ potency: 5
+ growthStages: 4
+ basicGrowth:
+ nutrientConsumption: 0.4
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 20
- PotencyDivisor: 20
+ min: 1
+ max: 20
+ potencyDivisor: 20
Rice:
- Min: 5
- Max: 20
- PotencyDivisor: 20
+ min: 5
+ max: 20
+ potencyDivisor: 20
- type: seed
id: soybeans
@@ -1526,19 +1674,21 @@
- FoodSoybeans
mutationPrototypes:
- koibean
- growthStages: 4
- lifespan: 25
- maturation: 6
- production: 6
- yield: 3
- potency: 5
- idealLight: 7
- nutrientConsumption: 0.40
+ growthComponents:
+ plant:
+ growthStages: 4
+ lifespan: 25
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 5
+ basicGrowth:
+ nutrientConsumption: 0.4
chemicals:
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: spacemansTrumpet
@@ -1549,22 +1699,25 @@
packetPrototype: SpacemansTrumpetSeeds
productPrototypes:
- FoodSpacemansTrumpet
- growthStages: 4
- lifespan: 20
- maturation: 14
- production: 3
- yield: 2
- potency: 10
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ growthStages: 4
+ lifespan: 20
+ maturation: 14
+ production: 3
+ yield: 2
+ potency: 10
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 50
+ min: 1
+ max: 5
+ potencyDivisor: 50
PolypyryliumOligomers:
- Min: 1
- Max: 15
- PotencyDivisor: 5
+ min: 1
+ max: 15
+ potencyDivisor: 5
- type: seed
id: koibean
@@ -1575,23 +1728,25 @@
packetPrototype: KoibeanSeeds
productPrototypes:
- FoodKoibean
- growthStages: 4
- lifespan: 25
- maturation: 6
- production: 6
- yield: 3
- potency: 5
- idealLight: 7
- nutrientConsumption: 0.40
+ growthComponents:
+ plant:
+ growthStages: 4
+ lifespan: 25
+ maturation: 6
+ production: 6
+ yield: 3
+ potency: 5
+ basicGrowth:
+ nutrientConsumption: 0.4
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
CarpoToxin:
- Min: 1
- Max: 4
- PotencyDivisor: 30
+ min: 1
+ max: 4
+ potencyDivisor: 30
- type: seed
id: grape
@@ -1602,21 +1757,23 @@
packetPrototype: GrapeSeeds
productPrototypes:
- FoodGrape
- lifespan: 50
- maturation: 6
- production: 5
- yield: 3
- potency: 10
- growthStages: 2
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 6
+ production: 5
+ yield: 3
+ potency: 10
+ growthStages: 2
chemicals:
Nutriment:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
- type: seed
id: watermelon
@@ -1629,25 +1786,26 @@
- FoodWatermelon
mutationPrototypes:
- holymelon
- lifespan: 55
- maturation: 12
- production: 3
- yield: 1
- potency: 1
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 12
+ production: 3
+ yield: 1
+ potency: 1
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Water:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: holymelon
@@ -1658,25 +1816,26 @@
packetPrototype: HolymelonSeeds
productPrototypes:
- FoodHolymelon
- lifespan: 55
- maturation: 12
- production: 3
- yield: 1
- potency: 1
- idealLight: 8
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 12
+ production: 3
+ yield: 1
+ potency: 1
chemicals:
Nutriment:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Holywater:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10
Vitamin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: cocoa
@@ -1687,24 +1846,28 @@
packetPrototype: CocoaSeeds
productPrototypes:
- FoodCocoaPod
- harvestRepeat: Repeat
- lifespan: 50
- maturation: 6
- production: 6
- yield: 6
- idealLight: 7
- waterConsumption: 1
- nutrientConsumption: 0.8
- idealHeat: 298
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 6
+ production: 6
+ yield: 6
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 1.0
+ nutrientConsumption: 0.8
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 25
+ min: 1
+ max: 4
+ potencyDivisor: 25
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: berries
@@ -1715,22 +1878,25 @@
packetPrototype: BerrySeeds
productPrototypes:
- FoodBerries
- harvestRepeat: Repeat
- lifespan: 50
- maturation: 6
- production: 6
- yield: 4
- idealLight: 7
- nutrientConsumption: 0.6
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 6
+ production: 6
+ yield: 4
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ nutrientConsumption: 0.6
chemicals:
Nutriment:
- Min: 2
- Max: 5
- PotencyDivisor: 30
+ min: 2
+ max: 5
+ potencyDivisor: 30
Vitamin:
- Min: 1
- Max: 4
- PotencyDivisor: 40
+ min: 1
+ max: 4
+ potencyDivisor: 40
- type: seed
id: bungo
@@ -1741,25 +1907,29 @@
packetPrototype: BungoSeeds
productPrototypes:
- FoodBungo
- harvestRepeat: Repeat
- lifespan: 50
- maturation: 8
- production: 6
- potency: 10
- yield: 3
- idealLight: 8
- idealHeat: 298
- growthStages: 4
- waterConsumption: 0.6
+ growthComponents:
+ plant:
+ lifespan: 50
+ maturation: 8
+ production: 6
+ potency: 10
+ yield: 3
+ growthStages: 4
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ waterConsumption: 0.6
+ atmosphericGrowth:
+ idealHeat: 298
chemicals:
Nutriment:
- Min: 5
- Max: 10
- PotencyDivisor: 20
+ min: 5
+ max: 10
+ potencyDivisor: 20
Enzyme:
- Min: 5
- Max: 10
- PotencyDivisor: 20
+ min: 5
+ max: 10
+ potencyDivisor: 20
- type: seed
id: pea
@@ -1772,25 +1942,27 @@
- FoodPeaPod
mutationPrototypes:
- laughinPea
- lifespan: 25
- growthStages: 3
- maturation: 8
- production: 6
- yield: 3
- potency: 25
- idealLight: 8
- harvestRepeat: Repeat
- nutrientConsumption: 0.5
- waterConsumption: 0.5
+ growthComponents:
+ plant:
+ lifespan: 25
+ growthStages: 3
+ maturation: 8
+ production: 6
+ yield: 3
+ potency: 25
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ nutrientConsumption: 0.5
chemicals:
Nutriment:
- Min: 1
- Max: 3
- PotencyDivisor: 33
+ min: 1
+ max: 3
+ potencyDivisor: 33
Vitamin:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: worldPea
@@ -1801,29 +1973,31 @@
packetPrototype: PeaSeeds
productPrototypes:
- FoodWorldPeas
- lifespan: 25
- growthStages: 3
- maturation: 20
- production: 6
- yield: 3
- potency: 25
- idealLight: 8
- harvestRepeat: Repeat
- nutrientConsumption: 0.5
- waterConsumption: 0.5
+ growthComponents:
+ plant:
+ lifespan: 25
+ growthStages: 3
+ maturation: 20
+ production: 6
+ yield: 3
+ potency: 25
+ harvest:
+ harvestRepeat: Repeat
+ basicGrowth:
+ nutrientConsumption: 0.5
chemicals:
Happiness:
- Min: 1
- Max: 3
- PotencyDivisor: 25
+ min: 1
+ max: 3
+ potencyDivisor: 25
Nutriment:
- Min: 1
- Max: 3
- PotencyDivisor: 20
+ min: 1
+ max: 3
+ potencyDivisor: 20
Pax:
- Min: 1
- Max: 2
- PotencyDivisor: 50
+ min: 1
+ max: 2
+ potencyDivisor: 50
- type: seed
id: pumpkin
@@ -1836,22 +2010,25 @@
- FoodPumpkin
mutationPrototypes:
- bluePumpkin
- lifespan: 55
- maturation: 10
- production: 4
- yield: 2
- potency: 10
- idealHeat: 288
- growthStages: 3
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 10
+ production: 4
+ yield: 2
+ potency: 10
+ growthStages: 3
+ atmosphericGrowth:
+ idealHeat: 288
chemicals:
PumpkinFlesh:
- Min: 1
- Max: 20
- PotencyDivisor: 5
+ min: 1
+ max: 20
+ potencyDivisor: 5
Vitamin:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
- type: seed
id: bluePumpkin
@@ -1862,26 +2039,29 @@
packetPrototype: BluePumpkinSeeds
productPrototypes:
- FoodBluePumpkin
- lifespan: 55
- maturation: 10
- production: 4
- yield: 2
- potency: 10
- idealHeat: 288
- growthStages: 3
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 10
+ production: 4
+ yield: 2
+ potency: 10
+ growthStages: 3
+ atmosphericGrowth:
+ idealHeat: 288
chemicals:
Ammonia:
- Min: 1
- Max: 15
- PotencyDivisor: 3
+ min: 1
+ max: 15
+ potencyDivisor: 3
Chlorine:
- Min: 1
- Max: 5
- PotencyDivisor: 5
+ min: 1
+ max: 5
+ potencyDivisor: 5
Vitamin:
- Min: 1
- Max: 10
- PotencyDivisor: 3
+ min: 1
+ max: 10
+ potencyDivisor: 3
- type: seed
id: cotton
@@ -1894,19 +2074,21 @@
- CottonBol
mutationPrototypes:
- pyrotton
- lifespan: 25
- maturation: 8
- production: 3
- yield: 3
- potency: 5
- idealLight: 8
- growthStages: 3
- waterConsumption: 0.60
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 3
+ yield: 3
+ potency: 5
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.6
chemicals:
Fiber:
- Min: 5
- Max: 10
- PotencyDivisor: 20
+ min: 5
+ max: 10
+ potencyDivisor: 20
- type: seed
id: pyrotton
@@ -1917,23 +2099,25 @@
packetPrototype: PyrottonSeeds
productPrototypes:
- PyrottonBol
- lifespan: 25
- maturation: 8
- production: 3
- yield: 2
- potency: 5
- idealLight: 8
- growthStages: 3
- waterConsumption: 0.80
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 8
+ production: 3
+ yield: 2
+ potency: 5
+ growthStages: 3
+ basicGrowth:
+ waterConsumption: 0.8
chemicals:
Fiber:
- Min: 5
- Max: 10
- PotencyDivisor: 20
+ min: 5
+ max: 10
+ potencyDivisor: 20
Phlogiston:
- Min: 4
- Max: 8
- PotencyDivisor: 30
+ min: 4
+ max: 8
+ potencyDivisor: 30
- type: seed
id: cherry
@@ -1944,22 +2128,24 @@
packetPrototype: CherrySeeds
productPrototypes:
- FoodCherry
- harvestRepeat: Repeat
- lifespan: 55
- maturation: 6
- production: 6
- yield: 5
- potency: 10
- idealLight: 6
+ growthComponents:
+ plant:
+ lifespan: 55
+ maturation: 6
+ production: 6
+ yield: 5
+ potency: 10
+ harvest:
+ harvestRepeat: Repeat
chemicals:
Nutriment:
- Min: 1
- Max: 3
- PotencyDivisor: 30
+ min: 1
+ max: 3
+ potencyDivisor: 30
Vitamin:
- Min: 1
- Max: 3
- PotencyDivisor: 40
+ min: 1
+ max: 3
+ potencyDivisor: 40
- type: seed
id: anomalyBerry
@@ -1970,27 +2156,30 @@
packetPrototype: AnomalyBerrySeeds
productPrototypes:
- FoodAnomalyBerry
- lifespan: 25
- maturation: 12
- production: 3
- yield: 3
- potency: 10
- growthStages: 2
- waterConsumption: 0.60
- nutrientConsumption: 0.50
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 12
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 2
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.5
chemicals:
Artifexium:
- Min: 1
- Max: 1
- PotencyDivisor: 4
+ min: 1
+ max: 1
+ potencyDivisor: 4
Nutriment:
- Min: 1
- Max: 2
- PotencyDivisor: 30
+ min: 1
+ max: 2
+ potencyDivisor: 30
Vitamin:
- Min: 1
- Max: 2
- PotencyDivisor: 40
+ min: 1
+ max: 2
+ potencyDivisor: 40
- type: seed
id: bloonion
@@ -2001,28 +2190,31 @@
packetPrototype: BloonionSeeds
productPrototypes:
- FoodBloonion
- lifespan: 25
- maturation: 15
- production: 3
- yield: 3
- potency: 10
- growthStages: 4
- waterConsumption: 0.60
- nutrientConsumption: 0.50
+ growthComponents:
+ plant:
+ lifespan: 25
+ maturation: 15
+ production: 3
+ yield: 3
+ potency: 10
+ growthStages: 4
+ basicGrowth:
+ waterConsumption: 0.6
+ nutrientConsumption: 0.5
chemicals:
Potassium:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Phosphorus:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Sugar:
- Min: 1
- Max: 5
- PotencyDivisor: 20
+ min: 1
+ max: 5
+ potencyDivisor: 20
Allicin:
- Min: 1
- Max: 10
- PotencyDivisor: 10
+ min: 1
+ max: 10
+ potencyDivisor: 10