mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
[Re-open] Botany Rework Part 2: GrowthComponents (#39311)
* Restarting files from scratch * reduce file count, more progress * Additional partial work * update 1 * update 2 * TODO execution * fix * Update PlantGrowthComponent.cs * fix 1 * growth correction * PlantGrowthComponent * update 99 * last update * oops * Update vegan-meatball.yml * Data loss * ToxinsComponent * Default components * minor fix * PlantTraitsComponent * fix error * fix error 2 * Copy growth components * last update within this PR * AutoHarvestGrowth remote * Viable * fix * Fix growth rate * Improvements to the YAML * merge update * fix * code cleanup * TODO * fix * oops * oops 2 * more TODO * fix names * fix names oops * Harvest bug fixes * fix tests * refactor: cleanup after mering master * Update ConsumeExudeGasGrowthSystem.cs * refactor: make this piece of happiness and ponies work * review * oops * fix <summary> * Separation of plant components into PlantComponent and PlantTraitsComponent and cleaning * EnsureUniqueSeed * refactor: change Resolve to TryComp where appropriate * refactor: review comments - try get for components, clenaups * refactor: minor yaml cleanups * fixing trifles --------- Co-authored-by: PraxisMapper <praxismapper@gmail.com> Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Atmospheric-related requirements for proper entity growth. Used in botany.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class AtmosphericGrowthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Ideal temperature for plant growth in Kelvin.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float IdealHeat = Atmospherics.T20C;
|
||||
|
||||
/// <summary>
|
||||
/// Temperature tolerance range around <see cref="IdealHeat"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HeatTolerance = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum pressure tolerance for plant growth.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LowPressureTolerance = 81f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum pressure tolerance for plant growth.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HighPressureTolerance = 121f;
|
||||
}
|
||||
21
Content.Server/Botany/Components/BasicGrowthComponent.cs
Normal file
21
Content.Server/Botany/Components/BasicGrowthComponent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Basic parameters for plant growth.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class BasicGrowthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of water consumed per growth tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WaterConsumption = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of nutrients consumed per growth tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float NutrientConsumption = 0.75f;
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
using System.Threading;
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
namespace Content.Server.Botany
|
||||
/// <summary>
|
||||
/// Anything that can be used to cross-pollinate plants.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class BotanySwabComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Anything that can be used to cross-pollinate plants.
|
||||
/// Delay between swab uses.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class BotanySwabComponent : Component
|
||||
{
|
||||
[DataField("swabDelay")]
|
||||
public float SwabDelay = 2f;
|
||||
[DataField]
|
||||
public TimeSpan SwabDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// SeedData from the first plant that got swabbed.
|
||||
/// </summary>
|
||||
public SeedData? SeedData;
|
||||
}
|
||||
/// <summary>
|
||||
/// SeedData from the first plant that got swabbed.
|
||||
/// </summary>
|
||||
public SeedData? SeedData;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Data for gas to consume/exude on plant growth.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class ConsumeExudeGasGrowthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of gases and their consumption rates per growth tick.
|
||||
/// </summary>
|
||||
[DataField] public Dictionary<Gas, float> ConsumeGasses = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of gases and their exude rates per growth tick.
|
||||
/// </summary>
|
||||
[DataField] public Dictionary<Gas, float> ExudeGasses = new();
|
||||
}
|
||||
87
Content.Server/Botany/Components/GrowthComponentsHolder.cs
Normal file
87
Content.Server/Botany/Components/GrowthComponentsHolder.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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();
|
||||
|
||||
/// <summary>
|
||||
/// Extra-traits.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PlantTraitsComponent? PlantTraits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plant characteristics.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PlantComponent? Plant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Basic properties for plant growth.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public BasicGrowthComponent? BasicGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// What defence plant have against toxins?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PlantToxinsComponent? Toxins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Harvesting process-related data.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PlantHarvestComponent? Harvest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Atmos-related environment requirements for plant growth.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public AtmosphericGrowthComponent? AtmosphericGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// What gases plant consume/exude upon growth.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ConsumeExudeGasGrowthComponent? ConsumeExudeGasGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Weeds and pests related data for plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public WeedPestGrowthComponent? WeedPestGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Damage tolerance of plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public UnviableGrowthComponent? UnviableGrowth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populates any null properties with default component instances so that
|
||||
/// systems can always apply a full set. Existing (YAML-provided) values are kept.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Content.Server/Botany/Components/PlantComponent.cs
Normal file
51
Content.Server/Botany/Components/PlantComponent.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for storing core plant data.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class PlantComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The plant's max health.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Endurance = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// How many produce are created on harvest.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Yield;
|
||||
|
||||
/// <summary>
|
||||
/// The number of growth ticks this plant can be alive for. Plants take high damage levels when Age > Lifespan.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Lifespan;
|
||||
|
||||
/// <summary>
|
||||
/// The number of growth ticks it takes for a plant to reach its final growth stage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Maturation;
|
||||
|
||||
/// <summary>
|
||||
/// The number of growth ticks it takes for a plant to be (re-)harvestable. Shouldn't be lower than Maturation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Production;
|
||||
|
||||
/// <summary>
|
||||
/// How many different sprites appear before the plant is fully grown.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int GrowthStages = 6;
|
||||
|
||||
/// <summary>
|
||||
/// A scalar for sprite size and chemical solution volume in the produce. Caps at 100.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Potency = 1f;
|
||||
}
|
||||
48
Content.Server/Botany/Components/PlantHarvestComponent.cs
Normal file
48
Content.Server/Botany/Components/PlantHarvestComponent.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Data for plant harvesting process.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class PlantHarvestComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Harvest repeat type.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HarvestType HarvestRepeat = HarvestType.NoRepeat;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the plant is currently ready for harvest.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool ReadyForHarvest = false;
|
||||
|
||||
/// <summary>
|
||||
/// The age of the plant when last harvested.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int LastHarvest = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Harvest options for plants.
|
||||
/// </summary>
|
||||
public enum HarvestType
|
||||
{
|
||||
/// <summary>
|
||||
/// Plant is removed on harvest.
|
||||
/// </summary>
|
||||
NoRepeat,
|
||||
|
||||
/// <summary>
|
||||
/// Plant makes produce every Production ticks.
|
||||
/// </summary>
|
||||
Repeat,
|
||||
|
||||
/// <summary>
|
||||
/// Repeat, plus produce is dropped on the ground near the plant automatically.
|
||||
/// </summary>
|
||||
SelfHarvest
|
||||
}
|
||||
@@ -4,6 +4,9 @@ using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Container for plant-holder and plant combined data.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PlantHolderComponent : Component
|
||||
{
|
||||
@@ -14,14 +17,8 @@ public sealed partial class PlantHolderComponent : Component
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Time between plant reagent consumption updates.
|
||||
/// Number of missing gases required for plant growth.
|
||||
/// </summary>
|
||||
[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);
|
||||
|
||||
/// <summary>
|
||||
/// Time between plant reagent consumption updates.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// Game time when the plant last did a growth update.
|
||||
/// </summary>
|
||||
@@ -43,6 +46,9 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public SoundSpecifier? WateringSound;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to update the sprite after the next update cycle.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool UpdateSpriteAfterUpdate;
|
||||
|
||||
@@ -53,33 +59,54 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public bool DrawWarnings = false;
|
||||
|
||||
/// <summary>
|
||||
/// Current water level in the plant holder (0-100).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WaterLevel = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Current nutrient level in the plant holder (0-100).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float NutritionLevel = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Current pest level in the plant holder (0-10).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PestLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Current weed level in the plant holder (0-10).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Current toxin level in the plant holder (0-100).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Toxins;
|
||||
|
||||
/// <summary>
|
||||
/// Current age of the plant in growth cycles.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Age;
|
||||
|
||||
/// <summary>
|
||||
/// Number of growth cycles to skip due to poor conditions.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int SkipAging;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the plant is dead.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Dead;
|
||||
|
||||
[DataField]
|
||||
public bool Harvest;
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for mutation chance and severity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MutationMod = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Current mutation level (0-100).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MutationLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Current health of the plant (0 to seed endurance).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Health;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for weed growth rate.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedCoefficient = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Seed data for the currently planted seed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
@@ -120,12 +162,6 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public bool ImproperPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Not currently used.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ImproperLight;
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the solution container that holds the soil/nutrient solution.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SoilSolutionName = "soil";
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the soil solution container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Entity<SolutionComponent>? SoilSolution = null;
|
||||
}
|
||||
|
||||
21
Content.Server/Botany/Components/PlantToxinsComponent.cs
Normal file
21
Content.Server/Botany/Components/PlantToxinsComponent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Data for plant resistance to toxins.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class PlantToxinsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum toxin level the plant can tolerate before taking damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ToxinsTolerance = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// Divisor for calculating toxin uptake rate. Higher values mean slower toxin processing.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ToxinUptakeDivisor = 10f;
|
||||
}
|
||||
49
Content.Server/Botany/Components/PlantTraitsComponent.cs
Normal file
49
Content.Server/Botany/Components/PlantTraitsComponent.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for managing special plant traits and mutations.
|
||||
/// </summary>
|
||||
/// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, produce can't be put into the seed maker.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Seedless = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, a sharp tool is required to harvest this plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Ligneous = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the plant can scream when harvested.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanScream = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the plant can turn into kudzu.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool TurnIntoKudzu = false;
|
||||
|
||||
/// <summary>
|
||||
/// Which kind of kudzu this plant will turn into if it kuzuifies.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId KudzuPrototype = "WeakKudzu";
|
||||
|
||||
/// <summary>
|
||||
/// If false, rapidly decrease health while growing. Adds a bit of challenge to keep mutated plants alive via Unviable's frequency.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Viable = true;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Produce-related data for plant and plant growth cycle.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(BotanySystem))]
|
||||
public sealed partial class ProduceComponent : SharedProduceComponent
|
||||
{
|
||||
[DataField("targetSolution")] public string SolutionName { get; set; } = "food";
|
||||
/// <summary>
|
||||
/// Name of the solution container that holds the produce's contents.
|
||||
/// </summary>
|
||||
[DataField("targetSolution")]
|
||||
public string SolutionName { get; set; } = "food";
|
||||
|
||||
/// <summary>
|
||||
/// Seed data used to create a <see cref="SeedComponent"/> when this produce has its seeds extracted.
|
||||
/// Seed data used to create a <see cref="SeedComponent"/> when this produce has its seeds extracted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seed data used to create a <see cref="SeedComponent"/> when this produce has its seeds extracted.
|
||||
/// Prototype ID for the seed that can be extracted from this produce.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SeedPrototype>))]
|
||||
public string? SeedId;
|
||||
[DataField]
|
||||
public ProtoId<SeedPrototype>? SeedId;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Data container for plant seed. Contains all info (values for components) for new plant to grow from seed.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(BotanySystem))]
|
||||
public sealed partial class SeedComponent : SharedSeedComponent
|
||||
{
|
||||
[RegisterComponent, Access(typeof(BotanySystem))]
|
||||
public sealed partial class SeedComponent : SharedSeedComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 <see
|
||||
/// cref="SeedId"/>.
|
||||
/// </summary>
|
||||
[DataField("seed")]
|
||||
public SeedData? Seed;
|
||||
/// <summary>
|
||||
/// 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 <see
|
||||
/// cref="SeedId"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, overrides the plant's initial health. Otherwise, the plant's initial health is set to the Endurance value.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float? HealthOverride = null;
|
||||
/// <summary>
|
||||
/// If not null, overrides the plant's initial health. Otherwise, the plant's initial health is set to the Endurance value.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float? HealthOverride = null;
|
||||
|
||||
/// <summary>
|
||||
/// Name of a base seed prototype that is used if <see cref="Seed"/> is null.
|
||||
/// </summary>
|
||||
[DataField("seedId", customTypeSerializer: typeof(PrototypeIdSerializer<SeedPrototype>))]
|
||||
public string? SeedId;
|
||||
}
|
||||
/// <summary>
|
||||
/// Name of a base seed prototype that is used if <see cref="Seed"/> is null.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SeedPrototype>? SeedId;
|
||||
}
|
||||
|
||||
15
Content.Server/Botany/Components/UnviableGrowthComponent.cs
Normal file
15
Content.Server/Botany/Components/UnviableGrowthComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Damage tolerance of plant.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class UnviableGrowthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of damage dealt to the plant per growth tick with unviable.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float UnviableDamage = 6f;
|
||||
}
|
||||
52
Content.Server/Botany/Components/WeedPestGrowthComponent.cs
Normal file
52
Content.Server/Botany/Components/WeedPestGrowthComponent.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace Content.Server.Botany.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[DataDefinition]
|
||||
public sealed partial class WeedPestGrowthComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum weed level the plant can tolerate before taking damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedTolerance = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum pest level the plant can tolerate before taking damage.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PestTolerance = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance per tick for weeds to grow around this plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedGrowthChance = 0.01f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of weed growth per successful weed growth tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedGrowthAmount = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Weed level threshold at which the plant is considered overgrown and will transform into kudzu.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WeedHighLevelThreshold = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance per tick for pests to damage this plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PestDamageChance = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of damage dealt to the plant per successful pest damage tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PestDamageAmount = 1f;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum amount of chemical that is added to produce, regardless of the potency
|
||||
/// </summary>
|
||||
[DataField("Min")] public FixedPoint2 Min = FixedPoint2.Epsilon;
|
||||
[DataField] public FixedPoint2 Min = FixedPoint2.Epsilon;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of chemical that can be produced after taking plant potency into account.
|
||||
/// </summary>
|
||||
[DataField("Max")] public FixedPoint2 Max;
|
||||
[DataField] public FixedPoint2 Max;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("PotencyDivisor")] public float PotencyDivisor;
|
||||
[DataField] public float PotencyDivisor;
|
||||
|
||||
/// <summary>
|
||||
/// Inherent chemical is one that is NOT result of mutation or crossbreeding. These chemicals are removed if species mutation is executed.
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// The name of this seed. Determines the name of seed packets.
|
||||
/// The name of this seed. Determines the name of seed packets.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
[DataField]
|
||||
public string Name { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("noun")]
|
||||
[DataField]
|
||||
public string Noun { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("displayName")]
|
||||
[DataField]
|
||||
public string DisplayName { get; private set; } = "";
|
||||
|
||||
[DataField("mysterious")] public bool Mysterious;
|
||||
[DataField] public bool Mysterious;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the properties of this seed cannot be modified.
|
||||
/// If true, the properties of this seed cannot be modified.
|
||||
/// </summary>
|
||||
[DataField("immutable")] public bool Immutable;
|
||||
[DataField]
|
||||
public bool Immutable;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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
|
||||
/// <summary>
|
||||
/// The entity prototype that is spawned when this type of seed is extracted from produce using a seed extractor.
|
||||
/// </summary>
|
||||
[DataField("packetPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string PacketPrototype = "SeedBase";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<EntProtoId> ProductPrototypes = new();
|
||||
|
||||
[DataField] public Dictionary<string, SeedChemQuantity> Chemicals = new();
|
||||
|
||||
[DataField] public Dictionary<Gas, float> ConsumeGasses = new();
|
||||
|
||||
[DataField] public Dictionary<Gas, float> 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";
|
||||
|
||||
/// <summary>
|
||||
/// If true, cannot be harvested for seeds. Balances hybrids and
|
||||
/// mutations.
|
||||
/// The entity prototypes that are spawned when this type of seed is harvested.
|
||||
/// </summary>
|
||||
[DataField] public bool Seedless = false;
|
||||
[DataField]
|
||||
public List<EntProtoId> ProductPrototypes = [];
|
||||
|
||||
/// <summary>
|
||||
/// If false, rapidly decrease health while growing. Used to kill off
|
||||
/// plants with "bad" mutations.
|
||||
/// </summary>
|
||||
[DataField] public bool Viable = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, a sharp tool is required to harvest this plant.
|
||||
/// </summary>
|
||||
[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<string, SeedChemQuantity> 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<EntityPrototype>))] public string KudzuPrototype = "WeakKudzu";
|
||||
|
||||
[DataField] public bool TurnIntoKudzu;
|
||||
[DataField] public string? SplatPrototype { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The mutation effects that have been applied to this plant.
|
||||
/// </summary>
|
||||
[DataField] public List<RandomPlantMutation> Mutations { get; set; } = new();
|
||||
[DataField]
|
||||
public List<RandomPlantMutation> Mutations { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The seed prototypes this seed may mutate into when prompted to.
|
||||
/// The seed prototypes this seed may mutate into when prompted to.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<SeedPrototype>> MutationPrototypes = new();
|
||||
public List<ProtoId<SeedPrototype>> MutationPrototypes = [];
|
||||
|
||||
/// <summary>
|
||||
/// Log impact for when the seed is planted.
|
||||
/// The growth components used by this seed.
|
||||
/// TODO: Delete after plants transition to entities
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LogImpact? PlantLogImpact = null;
|
||||
public GrowthComponentsHolder GrowthComponents = new();
|
||||
|
||||
/// <summary>
|
||||
/// Log impact for when the seed is harvested.
|
||||
/// Log impact for harvest operations.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LogImpact? HarvestLogImpact = null;
|
||||
public LogImpact? HarvestLogImpact;
|
||||
|
||||
/// <summary>
|
||||
/// Log impact for plant operations.
|
||||
/// </summary>
|
||||
[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<ISerializationManager>();
|
||||
|
||||
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<EntProtoId>(ProductPrototypes),
|
||||
MutationPrototypes = new List<ProtoId<SeedPrototype>>(MutationPrototypes),
|
||||
Chemicals = new Dictionary<string, SeedChemQuantity>(Chemicals),
|
||||
ConsumeGasses = new Dictionary<Gas, float>(ConsumeGasses),
|
||||
ExudeGasses = new Dictionary<Gas, float>(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<RandomPlantMutation>(),
|
||||
Mutations = new List<RandomPlantMutation>(Mutations),
|
||||
|
||||
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
||||
Unique = true,
|
||||
@@ -321,8 +190,12 @@ public partial class SeedData
|
||||
/// </summary>
|
||||
public SeedData SpeciesChange(SeedData other)
|
||||
{
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
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<ProtoId<SeedPrototype>>(other.MutationPrototypes),
|
||||
|
||||
Chemicals = new Dictionary<string, SeedChemQuantity>(Chemicals),
|
||||
ConsumeGasses = new Dictionary<Gas, float>(ConsumeGasses),
|
||||
ExudeGasses = new Dictionary<Gas, float>(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,
|
||||
|
||||
55
Content.Server/Botany/Systems/AtmosphericGrowthSystem.cs
Normal file
55
Content.Server/Botany/Systems/AtmosphericGrowthSystem.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Applies atmospheric temperature and pressure effects to plants during growth ticks.
|
||||
/// Uses current tile gas mixture to penalize or clear warnings based on tolerances.
|
||||
/// </summary>
|
||||
public sealed class AtmosphericGrowthSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AtmosphericGrowthComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<AtmosphericGrowthComponent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
192
Content.Server/Botany/Systems/BasicGrowthSystem.cs
Normal file
192
Content.Server/Botany/Systems/BasicGrowthSystem.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.Swab;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles baseline plant progression each growth tick: aging, resource consumption,
|
||||
/// simple viability checks, and basic swab cross-pollination behavior.
|
||||
/// </summary>
|
||||
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.
|
||||
/// <summary>
|
||||
/// Multiplier for plant growth speed in hydroponics.
|
||||
/// </summary>
|
||||
public const float HydroponicsSpeedMultiplier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for resource consumption (water, nutrients) in hydroponics.
|
||||
/// </summary>
|
||||
public const float HydroponicsConsumptionMultiplier = 2f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<BasicGrowthComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
SubscribeLocalEvent<BasicGrowthComponent, BotanySwabDoAfterEvent>(OnSwab);
|
||||
}
|
||||
|
||||
private void OnSwab(Entity<BasicGrowthComponent> ent, ref BotanySwabDoAfterEvent args)
|
||||
{
|
||||
var component = ent.Comp;
|
||||
|
||||
if (args.Cancelled || args.Handled || args.Used == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<BotanySwabComponent>(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<BasicGrowthComponent> ent, ref OnPlantGrowEvent args)
|
||||
{
|
||||
var (uid, component) = ent;
|
||||
|
||||
if (!TryComp(uid, out PlantHolderComponent? holder)
|
||||
|| !TryComp<PlantTraitsComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Affects the growth of a plant by modifying its age or production timing.
|
||||
/// </summary>
|
||||
public void AffectGrowth(Entity<PlantHolderComponent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event of plant growing ticking.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct OnPlantGrowEvent;
|
||||
@@ -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<BotanySwabComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<BotanySwabComponent, BotanySwabDoAfterEvent>(OnDoAfter);
|
||||
@@ -44,7 +45,7 @@ public sealed class BotanySwabSystem : EntitySystem
|
||||
if (args.Target == null || !args.CanReach || !HasComp<PlantHolderComponent>(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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<SeedComponent>(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<EntityUid> AutoHarvest(SeedData proto, EntityCoordinates position, int yieldMod = 1)
|
||||
public IEnumerable<EntityUid> 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<EntityUid>();
|
||||
return [];
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> Harvest(SeedData proto, EntityUid user, int yieldMod = 1)
|
||||
public IEnumerable<EntityUid> 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<EntityUid>();
|
||||
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<EntityUid> GenerateProduct(SeedData proto, EntityCoordinates position, int yieldMod = 1)
|
||||
public IEnumerable<EntityUid> GenerateProduct(SeedData proto, EntityCoordinates position, EntityUid plantEntity)
|
||||
{
|
||||
if (!TryGetPlant(proto, out var plant))
|
||||
return [];
|
||||
|
||||
var yieldMod = Comp<PlantHolderComponent>(plantEntity).YieldMod;
|
||||
var harvest = Comp<PlantHarvestComponent>(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<EntityUid>();
|
||||
|
||||
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<ProduceComponent>(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<SharpComponent>(held);
|
||||
if (!TryGetPlantTraits(proto, out var traits))
|
||||
return true;
|
||||
|
||||
return !traits.Ligneous || traits.Ligneous && held != null && HasComp<SharpComponent>(held);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
64
Content.Server/Botany/Systems/ConsumeExudeGasGrowthSystem.cs
Normal file
64
Content.Server/Botany/Systems/ConsumeExudeGasGrowthSystem.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Consumes and emits configured gases around plants each growth tick, then merges
|
||||
/// the adjusted gas mixture back into the environment.
|
||||
/// </summary>
|
||||
public sealed class ConsumeExudeGasGrowthSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ConsumeExudeGasGrowthComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<ConsumeExudeGasGrowthComponent> 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<RandomPlantMutationListPrototype> RandomPlantMutations = "RandomPlantMutations";
|
||||
private static readonly ProtoId<RandomPlantMutationListPrototype> 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
|
||||
/// <summary>
|
||||
/// For each random mutation, see if it occurs on this plant this check.
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
/// <param name="severity"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the plant has all the growth components specified in the seed data.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
151
Content.Server/Botany/Systems/PlantHarvestSystem.cs
Normal file
151
Content.Server/Botany/Systems/PlantHarvestSystem.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Manages harvest readiness and execution for plants, including repeat/self-harvest
|
||||
/// logic and produce spawning, responding to growth and interaction events.
|
||||
/// </summary>
|
||||
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<PlantHarvestComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
SubscribeLocalEvent<PlantHarvestComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<PlantHarvestComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<PlantHarvestComponent> 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<PlantHarvestComponent> 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<PlantHarvestComponent> 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<PlantHarvestComponent, PlantHolderComponent> 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<PlantHarvestComponent> 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<PlantHarvestComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-harvests a plant.
|
||||
/// </summary>
|
||||
public void AutoHarvest(Entity<PlantHarvestComponent, PlantHolderComponent> ent)
|
||||
{
|
||||
if (!ent.Comp1.ReadyForHarvest || ent.Comp2.Seed == null)
|
||||
return;
|
||||
|
||||
_botany.AutoHarvest(ent.Comp2.Seed, Transform(ent.Owner).Coordinates, ent);
|
||||
AfterHarvest(ent);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
43
Content.Server/Botany/Systems/PlantSystem.cs
Normal file
43
Content.Server/Botany/Systems/PlantSystem.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Server.Botany.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles plant behavior and growth processing.
|
||||
/// </summary>
|
||||
public sealed class PlantSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PlantComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<PlantComponent> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the potency of a plant component.
|
||||
/// </summary>
|
||||
public void AdjustPotency(Entity<PlantComponent> ent, float delta)
|
||||
{
|
||||
var plant = ent.Comp;
|
||||
plant.Potency = Math.Max(plant.Potency + delta, 1);
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
37
Content.Server/Botany/Systems/PlantToxinsSystem.cs
Normal file
37
Content.Server/Botany/Systems/PlantToxinsSystem.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Server.Botany.Components;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles toxin accumulation and tolerance for plants, applying health damage
|
||||
/// and decrementing toxins based on per-tick uptake.
|
||||
/// </summary>
|
||||
public sealed class ToxinsSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PlantToxinsComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<PlantToxinsComponent> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
28
Content.Server/Botany/Systems/UnviableGrowthSystem.cs
Normal file
28
Content.Server/Botany/Systems/UnviableGrowthSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Server.Botany.Components;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Applies a death chance and damage to unviable plants each growth tick, updating visuals when necessary.
|
||||
/// </summary>
|
||||
public sealed class UnviableGrowthSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<UnviableGrowthComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<UnviableGrowthComponent> 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;
|
||||
}
|
||||
}
|
||||
80
Content.Server/Botany/Systems/WeedPestGrowthSystem.cs
Normal file
80
Content.Server/Botany/Systems/WeedPestGrowthSystem.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Manages weed growth and pest damage per growth tick, and handles tray-level
|
||||
/// weed spawning and kudzu transformation based on conditions.
|
||||
/// </summary>
|
||||
public sealed class WeedPestGrowthSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<WeedPestGrowthComponent, OnPlantGrowEvent>(OnPlantGrow);
|
||||
SubscribeLocalEvent<PlantHolderComponent, OnPlantGrowEvent>(OnTrayUpdate);
|
||||
}
|
||||
|
||||
private void OnPlantGrow(Entity<WeedPestGrowthComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles weed growth and kudzu transformation for plant holder trays.
|
||||
/// </summary>
|
||||
private void OnTrayUpdate(Entity<PlantHolderComponent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,21 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that sets plant potency.
|
||||
/// </summary>
|
||||
public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustPotency>
|
||||
{
|
||||
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||
[Dependency] private readonly PlantSystem _plant = default!;
|
||||
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPotency> 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<PlantComponent>(entity, out var plant))
|
||||
return;
|
||||
|
||||
_plant.AdjustPotency((entity.Owner, plant), args.Effect.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,18 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that increments plant age / growth cycle.
|
||||
/// </summary>
|
||||
public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAffectGrowth>
|
||||
{
|
||||
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||
[Dependency] private readonly BasicGrowthSystem _plantGrowth = default!;
|
||||
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAffectGrowth> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Component>(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<float>(currentValue, out var floatVal))
|
||||
{
|
||||
MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
|
||||
field.SetValue(growthComp, floatVal);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetValue<int>(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<bool>(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<T>(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))
|
||||
{
|
||||
|
||||
@@ -5,6 +5,9 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that reverts aging of plant.
|
||||
/// </summary>
|
||||
public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantCryoxadone>
|
||||
{
|
||||
[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<PlantComponent>(entity, out var plant) || !TryComp<PlantHarvestComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that removes ability to get seeds from plant using seed maker.
|
||||
/// </summary>
|
||||
public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDestroySeeds>
|
||||
{
|
||||
[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<PlantTraitsComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that enhances plant longevity and endurance.
|
||||
/// </summary>
|
||||
public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDiethylamine>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDiethylamine> 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<PlantComponent>(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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that mutates plant to lose health with time.
|
||||
/// </summary>
|
||||
public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantPhalanximine>
|
||||
{
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantPhalanximine> 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<PlantTraitsComponent>(entity, out var traits))
|
||||
traits.Viable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entity effect that restores ability to get seeds from plant seed maker.
|
||||
/// </summary>
|
||||
public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantRestoreSeeds>
|
||||
{
|
||||
[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<PlantTraitsComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,28 +14,26 @@ namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||
public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, RobustHarvest>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<RobustHarvest> 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<PlantComponent>(entity, out var plant) || !TryComp<PlantTraitsComponent>(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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Plant mutation entity effect that forces plant to exude gas while living.
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateExudeGases>
|
||||
{
|
||||
[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<ConsumeExudeGasGrowthComponent>(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<Gas>().ToList());
|
||||
var gas = _random.Pick(Enum.GetValues<Gas>());
|
||||
|
||||
if (!gasses.TryAdd(gas, amount))
|
||||
{
|
||||
@@ -29,6 +32,9 @@ public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffe
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plant mutation entity effect that forces plant to consume gas while living.
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateConsumeGases>
|
||||
{
|
||||
[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<ConsumeExudeGasGrowthComponent>(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<Gas>().ToList());
|
||||
var gas = _random.Pick(Enum.GetValues<Gas>());
|
||||
|
||||
if (!gasses.TryAdd(gas, amount))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Plant mutation entity effect that changes repeatability of plant harvesting (without re-planting).
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateHarvest>
|
||||
{
|
||||
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateHarvest> 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<PlantHarvestComponent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityEffects.Effects.Botany;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,6 +12,15 @@ public sealed partial class PlantMutateConsumeGases : EntityEffectBase<PlantMuta
|
||||
|
||||
[DataField]
|
||||
public float MaxValue = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<PlantMutateExudeGases>
|
||||
@@ -19,4 +30,13 @@ public sealed partial class PlantMutateExudeGases : EntityEffectBase<PlantMutate
|
||||
|
||||
[DataField]
|
||||
public float MaxValue = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return Loc.GetString("entity-effect-guidebook-plant-mutate-exude-gasses",
|
||||
("chance", Probability),
|
||||
("minValue", MinValue),
|
||||
("maxValue", MaxValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user