diff --git a/Content.Client/Botany/Components/PlantVisualsComponent.cs b/Content.Client/Botany/Components/PlantVisualsComponent.cs new file mode 100644 index 00000000000000..fc099ef9148b0b --- /dev/null +++ b/Content.Client/Botany/Components/PlantVisualsComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Client.Botany.Components; + +[RegisterComponent] +public sealed partial class PlantVisualsComponent : Component +{ +} diff --git a/Content.Client/Botany/PlantHolderVisualizerSystem.cs b/Content.Client/Botany/PlantHolderVisualizerSystem.cs index 963d259c40855d..02b35553347d61 100644 --- a/Content.Client/Botany/PlantHolderVisualizerSystem.cs +++ b/Content.Client/Botany/PlantHolderVisualizerSystem.cs @@ -26,8 +26,8 @@ protected override void OnAppearanceChange(EntityUid uid, PlantHolderVisualsComp if (args.Sprite == null) return; - if (AppearanceSystem.TryGetData(uid, PlantHolderVisuals.PlantRsi, out var rsi, args.Component) - && AppearanceSystem.TryGetData(uid, PlantHolderVisuals.PlantState, out var state, args.Component)) + if (AppearanceSystem.TryGetData(uid, PlantVisuals.PlantRsi, out var rsi, args.Component) + && AppearanceSystem.TryGetData(uid, PlantVisuals.PlantState, out var state, args.Component)) { var valid = !string.IsNullOrWhiteSpace(state); diff --git a/Content.Client/Botany/PlantVisualizerSystem.cs b/Content.Client/Botany/PlantVisualizerSystem.cs new file mode 100644 index 00000000000000..98e55db9098015 --- /dev/null +++ b/Content.Client/Botany/PlantVisualizerSystem.cs @@ -0,0 +1,48 @@ +using Content.Client.Botany.Components; +using Content.Shared.Botany; +using Robust.Client.GameObjects; + +namespace Content.Client.Botany; + +public sealed class PlantVisualizerSystem : VisualizerSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnComponentInit); + } + + private void OnComponentInit(EntityUid uid, PlantVisualsComponent component, ComponentInit args) + { + if (!TryComp(uid, out var sprite)) + return; + + sprite.LayerMapReserveBlank(PlantLayers.Plant); + sprite.LayerSetVisible(PlantLayers.Plant, false); + } + + protected override void OnAppearanceChange(EntityUid uid, PlantVisualsComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (AppearanceSystem.TryGetData(uid, PlantVisuals.PlantRsi, out var rsi, args.Component) + && AppearanceSystem.TryGetData(uid, PlantVisuals.PlantState, out var state, args.Component)) + { + var valid = !string.IsNullOrWhiteSpace(state); + + args.Sprite.LayerSetVisible(PlantLayers.Plant, valid); + + if (valid) + { + args.Sprite.LayerSetRSI(PlantLayers.Plant, rsi); + args.Sprite.LayerSetState(PlantLayers.Plant, state); + } + } + } +} + +public enum PlantLayers : byte +{ + Plant +} diff --git a/Content.Server/Botany/Components/PlantComponent.cs b/Content.Server/Botany/Components/PlantComponent.cs new file mode 100644 index 00000000000000..8d8420950ba58c --- /dev/null +++ b/Content.Server/Botany/Components/PlantComponent.cs @@ -0,0 +1,94 @@ +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.Botany.Components; + +[RegisterComponent] +[Access(typeof(BotanySystem), typeof(PlantSystem), typeof(PlantHolderSystem), typeof(EntityEffect))] +public sealed partial class PlantComponent : Component +{ + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; + + /// + /// How often to check for visual updates. Intentionally happens faster than growth ticks. + /// + [DataField] + public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); + + /// + /// The last time this plant was eligible to create produce. Used to determine time between repeated harvests. + /// + [DataField] + public int LastProduce; + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan LastCycle = TimeSpan.Zero; + + /// + /// The time between growth ticks. + /// + [DataField] + public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f); + + /// + /// How many growth ticks this plant has been growing. + /// + [DataField] + public int Age = 1; + + /// + /// Dead plants don't do anything. They take up space until they're removed from the holder. + /// + [DataField] + public bool Dead; + + /// + /// If true, the plant will be harvested for produce upon empty-hand interaction. + /// + [DataField] + public bool Harvest; + + /// + /// The number of ticks to skip the age checks on. Makes it take longer to get produce as this is incremented. + /// + [DataField] + public int SkipAging; + + /// + /// Indicates if this plant has has clippers used on it to make seeds. + /// + [DataField] + public bool Sampled; + + /// + /// An additional multiplier to boost severity by when doing mutation checks. + /// + [DataField] + public float MutationMod = 1f; + + /// + /// The base multiplier to use for mutation checks. Equal to the units of mutagen present in the plant when rolling. + /// + [DataField] + public float MutationLevel; + + /// + /// The plant's current health. Can't exceed the seed's Endurance + /// + [DataField] + public float Health = 100; + + /// + /// Which PlantHolder this plant is growing in. Plants are expected to have a PlantHolder with the water and nutrient they use to grow. + /// + [DataField] + public EntityUid? PlantHolderUid; + + /// + /// The seed data for this plant, that determines most of it's growth values and produce. + /// + [DataField] + public SeedData? Seed; +} diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index f0661e4a301f8f..6e1c973ca161b0 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -9,11 +9,9 @@ public sealed partial class PlantHolderComponent : Component { [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan NextUpdate = TimeSpan.Zero; - [DataField] - public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); [DataField] - public int LastProduce; + public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); [DataField] public int MissingGas; @@ -48,48 +46,15 @@ public sealed partial class PlantHolderComponent : Component [DataField] public float Toxins; - [DataField] - public int Age; - - [DataField] - public int SkipAging; - - [DataField] - public bool Dead; - - [DataField] - public bool Harvest; - - [DataField] - public bool Sampled; - - [DataField] - public int YieldMod = 1; - - [DataField] - public float MutationMod = 1f; - - [DataField] - public float MutationLevel; - - [DataField] - public float Health; - [DataField] public float WeedCoefficient = 1f; - [DataField] - public SeedData? Seed; - [DataField] public bool ImproperHeat; [DataField] public bool ImproperPressure; - [DataField] - public bool ImproperLight; - [DataField] public bool ForceUpdate; @@ -98,4 +63,7 @@ public sealed partial class PlantHolderComponent : Component [DataField] public Entity? SoilSolution = null; + + [DataField] + public EntityUid? PlantUid; } diff --git a/Content.Server/Botany/Components/SeedComponent.cs b/Content.Server/Botany/Components/SeedComponent.cs index ffa1b7ef4e98bb..045e1d888d2031 100644 --- a/Content.Server/Botany/Components/SeedComponent.cs +++ b/Content.Server/Botany/Components/SeedComponent.cs @@ -1,10 +1,11 @@ using Content.Server.Botany.Systems; using Content.Shared.Botany.Components; +using Content.Shared.EntityEffects; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Botany.Components { - [RegisterComponent, Access(typeof(BotanySystem))] + [RegisterComponent, Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(PlantSystem), typeof(EntityEffect), typeof(BotanySwabSystem))] public sealed partial class SeedComponent : SharedSeedComponent { /// diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index 5608338f22e8d4..786afc572d989a 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -24,36 +24,6 @@ public enum HarvestType : byte 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 { @@ -81,7 +51,7 @@ public partial struct SeedChemQuantity // TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component. [Virtual, DataDefinition] -[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffect), typeof(MutationSystem))] +[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffect), typeof(MutationSystem), typeof(PlantSystem), typeof(BotanySwabSystem))] public partial class SeedData { #region Tracking @@ -89,23 +59,26 @@ public partial class SeedData /// /// The name of this seed. Determines the name of seed packets. /// - [DataField("name")] + [DataField] public string Name { get; private set; } = ""; /// /// The noun for this type of seeds. E.g. for fungi this should probably be "spores" instead of "seeds". Also /// used to determine the name of seed packets. /// - [DataField("noun")] + [DataField] public string Noun { get; private set; } = ""; /// /// Name displayed when examining the hydroponics tray. Describes the actual plant, not the seed itself. /// - [DataField("displayName")] + [DataField] public string DisplayName { get; private set; } = ""; - [DataField("mysterious")] public bool Mysterious; + /// + /// Adds an extra line to the end of the seed description if set. Currently unused. + /// + [DataField] public bool Mysterious; /// /// If true, the properties of this seed cannot be modified. @@ -124,13 +97,13 @@ public partial class SeedData /// /// The entity prototype that is spawned when this type of seed is extracted from produce using a seed extractor. /// - [DataField("packetPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string PacketPrototype = "SeedBase"; /// /// The entity prototype this seed spawns when it gets harvested. /// - [DataField("productPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] public List ProductPrototypes = new(); [DataField] public Dictionary Chemicals = new(); @@ -142,51 +115,70 @@ public partial class SeedData #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 + /// + /// The plant's max health. + /// [DataField] public float Endurance = 100f; + /// + /// How many produce are created on harvest. + /// [DataField] public int Yield; + + /// + /// The number of growth ticks this plant can be alive for. Plants take high damage levels when Age > Lifespan. + /// [DataField] public float Lifespan; + + /// + /// The number of growth ticks it takes for a plant to reach its final growth stage. + /// [DataField] public float Maturation; + + /// + /// The number of growth ticks it takes for a plant to be (re-)harvestable. Shouldn't be lower than Maturation. + /// [DataField] public float Production; + + /// + /// How many different sprites appear before the plant is fully grown. + /// [DataField] public int GrowthStages = 6; + /// + /// Harvest options are NoRepeat(plant is removed on harvest), Repeat(Plant makes produce every Production ticks), + /// and SelfHarvest (Repeat, plus produce is dropped on the ground near the plant automatically) + /// [DataField] public HarvestType HarvestRepeat = HarvestType.NoRepeat; + /// + /// A scalar for sprite size and chemical quantity on the produce. Caps at 100. + /// [DataField] public float Potency = 1f; /// - /// If true, cannot be harvested for seeds. Balances hybrids and - /// mutations. + /// If true, produce can't be put into the seed maker. /// [DataField] public bool Seedless = false; /// - /// If false, rapidly decrease health while growing. Used to kill off - /// plants with "bad" mutations. + /// If false, rapidly decrease health while growing. Adds a bit of challenge to keep mutated plants alive via Unviable's frequency. /// [DataField] public bool Viable = true; @@ -195,19 +187,6 @@ public partial class SeedData /// [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; } - #endregion #region Cosmetics @@ -223,12 +202,20 @@ public partial class SeedData [DataField] public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("PlantScreams", AudioParams.Default.WithVolume(-10)); + /// + /// If true, AAAAAAAAAAAHHHHHHHHHHH! + /// [DataField("screaming")] public bool CanScream; + /// + /// Which kind of kudzu this plant will turn into if it kuzuifies. + /// [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string KudzuPrototype = "WeakKudzu"; + /// + /// If true, this plant turns into it's KudzuPrototype when the PlantHolder's WeedLevel hits this plant's WeedHighLevelThreshold. + /// [DataField] public bool TurnIntoKudzu; - [DataField] public string? SplatPrototype { get; set; } #endregion @@ -243,6 +230,9 @@ public partial class SeedData [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] public List MutationPrototypes = new(); + /// + /// Copies this seed data to a new object. Required so mutations don't get applied to all plants of the same type. + /// public SeedData Clone() { DebugTools.Assert(!Immutable, "There should be no need to clone an immutable seed."); @@ -265,8 +255,6 @@ public SeedData Clone() WaterConsumption = WaterConsumption, IdealHeat = IdealHeat, HeatTolerance = HeatTolerance, - IdealLight = IdealLight, - LightTolerance = LightTolerance, ToxinsTolerance = ToxinsTolerance, LowPressureTolerance = LowPressureTolerance, HighPressureTolerance = HighPressureTolerance, @@ -290,7 +278,6 @@ public SeedData Clone() PlantIconState = PlantIconState, CanScream = CanScream, TurnIntoKudzu = TurnIntoKudzu, - SplatPrototype = SplatPrototype, Mutations = new List(), // Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified. @@ -301,7 +288,6 @@ public SeedData Clone() return newSeed; } - /// /// Handles copying most species defining data from 'other' to this seed while keeping the accumulated mutations intact. /// @@ -326,8 +312,6 @@ public SeedData SpeciesChange(SeedData other) WaterConsumption = WaterConsumption, IdealHeat = IdealHeat, HeatTolerance = HeatTolerance, - IdealLight = IdealLight, - LightTolerance = LightTolerance, ToxinsTolerance = ToxinsTolerance, LowPressureTolerance = LowPressureTolerance, HighPressureTolerance = HighPressureTolerance, @@ -353,11 +337,11 @@ public SeedData SpeciesChange(SeedData other) 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, }; + newSeed.Mutations.AddRange(Mutations); // Adding the new chemicals from the new species. foreach (var otherChem in other.Chemicals) diff --git a/Content.Server/Botany/Systems/BotanySwabSystem.cs b/Content.Server/Botany/Systems/BotanySwabSystem.cs index e8c7af92c27bd3..9aa35565b5ed93 100644 --- a/Content.Server/Botany/Systems/BotanySwabSystem.cs +++ b/Content.Server/Botany/Systems/BotanySwabSystem.cs @@ -41,7 +41,7 @@ private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent a /// private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args) { - if (args.Target == null || !args.CanReach || !HasComp(args.Target)) + if (args.Target == null || !args.CanReach || !HasComp(args.Target)) return; _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, swab.SwabDelay, new BotanySwabDoAfterEvent(), uid, target: args.Target, used: uid) @@ -57,21 +57,25 @@ private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInter /// private void OnDoAfter(EntityUid uid, BotanySwabComponent swab, DoAfterEvent args) { - if (args.Cancelled || args.Handled || !TryComp(args.Args.Target, out var plant)) + if (args.Cancelled || args.Handled || !TryComp(args.Args.Target, out var plant)) + return; + + var seed = plant.Seed; + if (seed == null) return; if (swab.SeedData == null) { // Pick up pollen - swab.SeedData = plant.Seed; + swab.SeedData = seed.Clone(); _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User); } else { - var old = plant.Seed; + var old = seed; if (old == null) return; - plant.Seed = _mutationSystem.Cross(swab.SeedData, old); // Cross-pollenate + 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); } diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs index 1487ed71d471c6..4b05b0a04bb818 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Seed.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs @@ -1,8 +1,8 @@ using Content.Server.Botany.Components; using Content.Server.Kitchen.Components; using Content.Server.Popups; -using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Botany; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Examine; using Content.Shared.Hands.EntitySystems; using Content.Shared.Popups; @@ -10,7 +10,6 @@ using Content.Shared.Random.Helpers; using Robust.Server.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Physics.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Diagnostics.CodeAnalysis; @@ -25,10 +24,8 @@ public sealed partial class BotanySystem : EntitySystem [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; - [Dependency] private readonly FixtureSystem _fixtureSystem = default!; [Dependency] private readonly RandomHelperSystem _randomHelper = default!; public override void Initialize() @@ -102,7 +99,7 @@ public EntityUid SpawnSeedPacket(SeedData proto, EntityCoordinates coords, Entit { var seed = Spawn(proto.PacketPrototype, coords); var seedComp = EnsureComp(seed); - seedComp.Seed = proto; + seedComp.Seed = proto.Clone(); seedComp.HealthOverride = healthOverride; var name = Loc.GetString(proto.Name); @@ -115,16 +112,16 @@ public EntityUid SpawnSeedPacket(SeedData proto, EntityCoordinates coords, Entit return seed; } - public IEnumerable AutoHarvest(SeedData proto, EntityCoordinates position, int yieldMod = 1) + public IEnumerable AutoHarvest(SeedData proto, EntityCoordinates position) { if (position.IsValid(EntityManager) && proto.ProductPrototypes.Count > 0) - return GenerateProduct(proto, position, yieldMod); + return GenerateProduct(proto, position); return Enumerable.Empty(); } - public IEnumerable Harvest(SeedData proto, EntityUid user, int yieldMod = 1) + public IEnumerable Harvest(SeedData proto, EntityUid user) { if (proto.ProductPrototypes.Count == 0 || proto.Yield <= 0) { @@ -134,28 +131,15 @@ public IEnumerable Harvest(SeedData proto, EntityUid user, int yieldM var name = Loc.GetString(proto.DisplayName); _popupSystem.PopupCursor(Loc.GetString("botany-harvest-success-message", ("name", name)), user, PopupType.Medium); - return GenerateProduct(proto, Transform(user).Coordinates, yieldMod); + return GenerateProduct(proto, Transform(user).Coordinates); } - public IEnumerable GenerateProduct(SeedData proto, EntityCoordinates position, int yieldMod = 1) + public IEnumerable GenerateProduct(SeedData proto, EntityCoordinates position) { - var totalYield = 0; - if (proto.Yield > -1) - { - if (yieldMod < 0) - totalYield = proto.Yield; - else - totalYield = proto.Yield * yieldMod; - - totalYield = Math.Max(1, totalYield); - } - var products = new List(); + proto.Unique = false; - if (totalYield > 1 || proto.HarvestRepeat != HarvestType.NoRepeat) - proto.Unique = false; - - for (var i = 0; i < totalYield; i++) + for (var i = 0; i < proto.Yield; i++) { var product = _robustRandom.Pick(proto.ProductPrototypes); diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs index 37b68d9475f1a6..236b036b26b5d4 100644 --- a/Content.Server/Botany/Systems/MutationSystem.cs +++ b/Content.Server/Botany/Systems/MutationSystem.cs @@ -21,17 +21,18 @@ public override void Initialize() /// /// For each random mutation, see if it occurs on this plant this check. /// - /// - /// - public void CheckRandomMutations(EntityUid plantHolder, ref SeedData seed, float severity) + public void CheckRandomMutations(EntityUid plant, ref SeedData seed, float severity) { foreach (var mutation in _randomMutations.mutations) { if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f))) { + if (!seed.Unique) + seed = seed.Clone(); + if (mutation.AppliesToPlant) { - var args = new EntityEffectBaseArgs(plantHolder, EntityManager); + var args = new EntityEffectBaseArgs(plant, EntityManager); mutation.Effect.Effect(args); } // Stat adjustments do not persist by being an attached effect, they just change the stat. @@ -41,20 +42,6 @@ public void CheckRandomMutations(EntityUid plantHolder, ref SeedData seed, float } } - /// - /// Checks all defined mutations against a seed to see which of them are applied. - /// - public void MutateSeed(EntityUid plantHolder, ref SeedData seed, float severity) - { - if (!seed.Unique) - { - Log.Error($"Attempted to mutate a shared seed"); - return; - } - - CheckRandomMutations(plantHolder, ref seed, severity); - } - public SeedData Cross(SeedData a, SeedData b) { SeedData result = b.Clone(); @@ -65,8 +52,6 @@ public SeedData Cross(SeedData a, SeedData b) 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); diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 271acb606a40a6..579741c90311cd 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -1,20 +1,16 @@ -using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; -using Content.Server.Kitchen.Components; using Content.Server.Popups; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Atmos; using Content.Shared.Botany; using Content.Shared.Burial.Components; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; using Content.Shared.Coordinates.Helpers; +using Content.Shared.EntityEffects; using Content.Shared.Examine; using Content.Shared.FixedPoint; -using Content.Shared.Hands.Components; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; -using Content.Shared.Random; using Content.Shared.Tag; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; @@ -27,19 +23,18 @@ namespace Content.Server.Botany.Systems; public sealed class PlantHolderSystem : EntitySystem { - [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly BotanySystem _botany = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly MutationSystem _mutation = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly RandomHelperSystem _randomHelper = default!; [Dependency] private readonly IRobustRandom _random = default!; - + [Dependency] private readonly PlantSystem _plant = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; public const float HydroponicsSpeedMultiplier = 1f; public const float HydroponicsConsumptionMultiplier = 2f; @@ -49,7 +44,6 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(OnSolutionTransferred); } @@ -68,43 +62,37 @@ public override void Update(float frameTime) } } - private int GetCurrentGrowthStage(Entity entity) - { - var (uid, component) = entity; - - if (component.Seed == null) - return 0; - - var result = Math.Max(1, (int)(component.Age * component.Seed.GrowthStages / component.Seed.Maturation)); - return result; - } - private void OnExamine(Entity entity, ref ExaminedEvent args) { if (!args.IsInDetailsRange) return; - var (_, component) = entity; - using (args.PushGroup(nameof(PlantHolderComponent))) { - if (component.Seed == null) + + var (_, component) = entity; + SeedData? seed = null; + var hasPlant = GetPlant(component.PlantUid, out var plant); + if (hasPlant) + seed = plant.Comp.Seed; + + if (!hasPlant) { args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message")); } - else if (!component.Dead) + else if (!plant.Comp.Dead && seed != null) { - var displayName = Loc.GetString(component.Seed.DisplayName); + var displayName = Loc.GetString(seed.DisplayName); args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", ("seedName", displayName), ("toBeForm", displayName.EndsWith('s') ? "are" : "is"))); - if (component.Health <= component.Seed.Endurance / 2) + if (plant.Comp.Health <= seed.Endurance / 2) { args.PushMarkup(Loc.GetString( "plant-holder-component-something-already-growing-low-health-message", ("healthState", - Loc.GetString(component.Age > component.Seed.Lifespan + Loc.GetString(plant.Comp.Age > seed.Lifespan ? "plant-holder-component-plant-old-adjective" : "plant-holder-component-plant-unhealthy-adjective")))); } @@ -130,9 +118,6 @@ private void OnExamine(Entity entity, ref ExaminedEvent ar if (component.Toxins > 40f) args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning")); - if (component.ImproperLight) - args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning")); - if (component.ImproperHeat) args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning")); @@ -145,48 +130,90 @@ private void OnExamine(Entity entity, ref ExaminedEvent ar } } + /// + /// Gets the PlantComponent on an Plant entity. + /// + private bool GetPlant(EntityUid? uid, out Entity plant) + { + if (uid != null) + { + if (TryComp(uid, out var plantComp) && plantComp != null) + { + plant = (uid.Value, plantComp); + return true; + } + } + plant = (EntityUid.Invalid, new PlantComponent()); + return false; + } + private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) { var (uid, component) = entity; + var hasPlant = GetPlant(component.PlantUid, out _); if (TryComp(args.Used, out SeedComponent? seeds)) { - if (component.Seed == null) + args.Handled = true; + if (seeds == null) + return; + if (!hasPlant) { if (!_botany.TryGetSeed(seeds, out var seed)) return; - args.Handled = true; - var name = Loc.GetString(seed.Name); - var noun = Loc.GetString(seed.Noun); - _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", - ("seedName", name), - ("seedNoun", noun)), args.User, PopupType.Medium); - - component.Seed = seed; - component.Dead = false; - component.Age = 1; - if (seeds.HealthOverride != null) - { - component.Health = seeds.HealthOverride.Value; - } - else + if (entity.Comp.PlantUid == null) { - component.Health = component.Seed.Endurance; - } - component.LastCycle = _gameTiming.CurTime; - - QueueDel(args.Used); - - CheckLevelSanity(uid, component); - UpdateSprite(uid, component); + seed = seed.Clone(); + var name = Loc.GetString(seed.Name); + var noun = Loc.GetString(seed.Noun); + _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", + ("seedName", name), + ("seedNoun", noun)), args.User, PopupType.Medium); + + var newPlant = Spawn("BasePlant", Transform(uid).Coordinates); + _meta.SetEntityName(newPlant, Loc.GetString(seed.DisplayName)); + var plantcomp = Comp(newPlant); + plantcomp.PlantHolderUid = uid; + plantcomp.Seed = seed; + entity.Comp.PlantUid = newPlant; + + var xform = Transform(newPlant); + _transform.SetParent(newPlant, xform, uid); + _plant.UpdateSprite(newPlant); + + foreach (var mutation in seed.Mutations) + { + if (mutation.AppliesToPlant) + { + var effectArgs = new EntityEffectBaseArgs(newPlant, EntityManager); + mutation.Effect.Effect(effectArgs); + } + } + + if (seeds.HealthOverride != null) + { + plantcomp.Health = seeds.HealthOverride.Value; + } + else + { + plantcomp.Health = seed.Endurance; + } + + component.LastCycle = _gameTiming.CurTime; + + QueueDel(args.Used); + UpdateSprite(uid, component); + } + } + else + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", + ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); return; } - args.Handled = true; - _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", - ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); return; } @@ -213,7 +240,7 @@ private void OnInteractUsing(Entity entity, ref InteractUs if (HasComp(args.Used)) { args.Handled = true; - if (component.Seed != null) + if (hasPlant == true) { _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message", ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); @@ -230,70 +257,6 @@ private void OnInteractUsing(Entity entity, ref InteractUs return; } - if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) - { - args.Handled = true; - if (component.Seed == null) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User); - return; - } - - if (component.Sampled) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User); - return; - } - - if (component.Dead) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User); - return; - } - - if (GetCurrentGrowthStage(entity) <= 1) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-early-sample-message"), args.User); - return; - } - - component.Health -= (_random.Next(3, 5) * 10); - - float? healthOverride; - if (component.Harvest) - { - healthOverride = null; - } - else - { - healthOverride = component.Health; - } - var packetSeed = component.Seed; - var seed = _botany.SpawnSeedPacket(packetSeed, Transform(args.User).Coordinates, args.User, healthOverride); - _randomHelper.RandomOffset(seed, 0.25f); - var displayName = Loc.GetString(component.Seed.DisplayName); - _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", - ("seedName", displayName)), args.User); - - DoScream(entity.Owner, component.Seed); - - if (_random.Prob(0.3f)) - component.Sampled = true; - - // Just in case. - CheckLevelSanity(uid, component); - ForceUpdateByExternalCause(uid, component); - - return; - } - - if (HasComp(args.Used)) - { - args.Handled = true; - DoHarvest(uid, args.User, component); - return; - } - if (TryComp(args.Used, out var produce)) { args.Handled = true; @@ -332,16 +295,6 @@ private void OnSolutionTransferred(Entity ent, ref Solutio { _audio.PlayPvs(ent.Comp.WateringSound, ent.Owner); } - private void OnInteractHand(Entity entity, ref InteractHandEvent args) - { - DoHarvest(entity, args.User, entity.Comp); - } - - public void WeedInvasion() - { - // TODO - } - public void Update(EntityUid uid, PlantHolderComponent? component = null) { @@ -363,50 +316,47 @@ public void Update(EntityUid uid, PlantHolderComponent? component = null) component.LastCycle = curTime; - // Process mutations - if (component.MutationLevel > 0) + SeedData? seed = null; + var hasPlant = TryComp(component.PlantUid, out var plant); + if (hasPlant) + { + TryComp(component.PlantUid, out var seedComp); + if (seedComp != null && seedComp.Seed != null) + seed = seedComp.Seed; + } + else // Plant was removed in a non-standard way, like admin direct delete. { - Mutate(uid, Math.Min(component.MutationLevel, 25), component); - component.UpdateSpriteAfterUpdate = true; - component.MutationLevel = 0; + component.PlantUid = null; } // Weeds like water and nutrients! They may appear even if there's not a seed planted. if (component.WaterLevel > 10 && component.NutritionLevel > 5) { var chance = 0f; - if (component.Seed == null) + if (seed == null) chance = 0.05f; - else if (component.Seed.TurnIntoKudzu) + else if (seed.TurnIntoKudzu) chance = 1f; else chance = 0.01f; if (_random.Prob(chance)) - component.WeedLevel += 1 + HydroponicsSpeedMultiplier * component.WeedCoefficient; + component.WeedLevel = Math.Clamp(component.WeedLevel + (1 + HydroponicsSpeedMultiplier * component.WeedCoefficient), 0, 10); if (component.DrawWarnings) component.UpdateSpriteAfterUpdate = true; } - if (component.Seed != null && component.Seed.TurnIntoKudzu - && component.WeedLevel >= component.Seed.WeedHighLevelThreshold) + if (plant != null && seed != null && seed.TurnIntoKudzu + && component.WeedLevel >= seed.WeedHighLevelThreshold) { - Spawn(component.Seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager)); - component.Seed.TurnIntoKudzu = false; - component.Health = 0; - } - - // There's a chance for a weed explosion to happen if weeds take over. - // Plants that are themselves weeds (WeedTolerance > 8) are unaffected. - if (component.WeedLevel >= 10 && _random.Prob(0.1f)) - { - if (component.Seed == null || component.WeedLevel >= component.Seed.WeedTolerance + 2) - WeedInvasion(); + Spawn(seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager)); + seed.TurnIntoKudzu = false; + plant.Health = 0; } // If we have no seed planted, or the plant is dead, stop processing here. - if (component.Seed == null || component.Dead) + if (seed == null || plant == null || plant.Dead) { if (component.UpdateSpriteAfterUpdate) UpdateSprite(uid, component); @@ -418,405 +368,31 @@ public void Update(EntityUid uid, PlantHolderComponent? component = null) // Can only happen when there's a live seed planted. if (_random.Prob(0.01f)) { - component.PestLevel += 0.5f * HydroponicsSpeedMultiplier; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Advance plant age here. - if (component.SkipAging > 0) - component.SkipAging--; - else - { - if (_random.Prob(0.8f)) - component.Age += (int)(1 * HydroponicsSpeedMultiplier); - - component.UpdateSpriteAfterUpdate = true; - } - - // Nutrient consumption. - if (component.Seed.NutrientConsumption > 0 && component.NutritionLevel > 0 && _random.Prob(0.75f)) - { - component.NutritionLevel -= MathF.Max(0f, component.Seed.NutrientConsumption * HydroponicsSpeedMultiplier); - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Water consumption. - if (component.Seed.WaterConsumption > 0 && component.WaterLevel > 0 && _random.Prob(0.75f)) - { - component.WaterLevel -= MathF.Max(0f, - component.Seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier); - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier; - - // Make sure genetics are viable. - if (!component.Seed.Viable) - { - AffectGrowth(uid, -1, component); - component.Health -= 6 * healthMod; - } - - // Prevents the plant from aging when lacking resources. - // Limits the effect on aging so that when resources are added, the plant starts growing in a reasonable amount of time. - if (component.SkipAging < 10) - { - // Make sure the plant is not starving. - if (component.NutritionLevel > 5) - { - component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod; - } - else - { - AffectGrowth(uid, -1, component); - component.Health -= healthMod; - } - - // Make sure the plant is not thirsty. - if (component.WaterLevel > 10) - { - component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod; - } - else - { - AffectGrowth(uid, -1, component); - component.Health -= healthMod; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas; - - component.MissingGas = 0; - if (component.Seed.ConsumeGasses.Count > 0) - { - foreach (var (gas, amount) in component.Seed.ConsumeGasses) - { - if (environment.GetMoles(gas) < amount) - { - component.MissingGas++; - continue; - } - - environment.AdjustMoles(gas, -amount); - } - - if (component.MissingGas > 0) - { - component.Health -= component.MissingGas * HydroponicsSpeedMultiplier; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - } - - // SeedPrototype pressure resistance. - var pressure = environment.Pressure; - if (pressure < component.Seed.LowPressureTolerance || pressure > component.Seed.HighPressureTolerance) - { - component.Health -= healthMod; - component.ImproperPressure = true; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - else - { - component.ImproperPressure = false; - } - - // SeedPrototype ideal temperature. - if (MathF.Abs(environment.Temperature - component.Seed.IdealHeat) > component.Seed.HeatTolerance) - { - component.Health -= healthMod; - component.ImproperHeat = true; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - else - { - component.ImproperHeat = false; - } - - // Gas production. - var exudeCount = component.Seed.ExudeGasses.Count; - if (exudeCount > 0) - { - foreach (var (gas, amount) in component.Seed.ExudeGasses) - { - environment.AdjustMoles(gas, - MathF.Max(1f, MathF.Round(amount * MathF.Round(component.Seed.Potency) / exudeCount))); - } - } - - // Toxin levels beyond the plant's tolerance cause damage. - // They are, however, slowly reduced over time. - if (component.Toxins > 0) - { - var toxinUptake = MathF.Max(1, MathF.Round(component.Toxins / 10f)); - if (component.Toxins > component.Seed.ToxinsTolerance) - { - component.Health -= toxinUptake; - } - - component.Toxins -= toxinUptake; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Weed levels. - if (component.PestLevel > 0) - { - // TODO: Carnivorous plants? - if (component.PestLevel > component.Seed.PestTolerance) - { - component.Health -= HydroponicsSpeedMultiplier; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Weed levels. - if (component.WeedLevel > 0) - { - // TODO: Parasitic plants. - if (component.WeedLevel >= component.Seed.WeedTolerance) - { - component.Health -= HydroponicsSpeedMultiplier; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - if (component.Age > component.Seed.Lifespan) - { - component.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier; + component.PestLevel = Math.Clamp(component.PestLevel + 0.5f * HydroponicsSpeedMultiplier, 0, 10); if (component.DrawWarnings) component.UpdateSpriteAfterUpdate = true; } - else if (component.Age < 0) // Revert back to seed packet! - { - var packetSeed = component.Seed; - // will put it in the trays hands if it has any, please do not try doing this - _botany.SpawnSeedPacket(packetSeed, Transform(uid).Coordinates, uid); - RemovePlant(uid, component); - component.ForceUpdate = true; - Update(uid, component); - return; - } - - CheckHealth(uid, component); - - if (component.Harvest && component.Seed.HarvestRepeat == HarvestType.SelfHarvest) - AutoHarvest(uid, component); - - // If enough time has passed since the plant was harvested, we're ready to harvest again! - if (!component.Dead && component.Seed.ProductPrototypes.Count > 0) - { - if (component.Age > component.Seed.Production) - { - if (component.Age - component.LastProduce > component.Seed.Production && !component.Harvest) - { - component.Harvest = true; - component.LastProduce = component.Age; - } - } - else - { - if (component.Harvest) - { - component.Harvest = false; - component.LastProduce = component.Age; - } - } - } - - CheckLevelSanity(uid, component); if (component.UpdateSpriteAfterUpdate) UpdateSprite(uid, component); } - //TODO: kill this bullshit - public void CheckLevelSanity(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed != null) - component.Health = MathHelper.Clamp(component.Health, 0, component.Seed.Endurance); - else - { - component.Health = 0f; - component.Dead = false; - } - - component.MutationLevel = MathHelper.Clamp(component.MutationLevel, 0f, 100f); - component.NutritionLevel = MathHelper.Clamp(component.NutritionLevel, 0f, 100f); - component.WaterLevel = MathHelper.Clamp(component.WaterLevel, 0f, 100f); - component.PestLevel = MathHelper.Clamp(component.PestLevel, 0f, 10f); - component.WeedLevel = MathHelper.Clamp(component.WeedLevel, 0f, 10f); - component.Toxins = MathHelper.Clamp(component.Toxins, 0f, 100f); - component.YieldMod = MathHelper.Clamp(component.YieldMod, 0, 2); - component.MutationMod = MathHelper.Clamp(component.MutationMod, 0f, 3f); - } - - public bool DoHarvest(EntityUid plantholder, EntityUid user, PlantHolderComponent? component = null) - { - if (!Resolve(plantholder, ref component)) - return false; - - if (component.Seed == null || Deleted(user)) - return false; - - - if (component.Harvest && !component.Dead) - { - if (TryComp(user, out var hands)) - { - if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity)) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user); - return false; - } - } - else if (!_botany.CanHarvest(component.Seed)) - { - return false; - } - - _botany.Harvest(component.Seed, user, component.YieldMod); - AfterHarvest(plantholder, component); - return true; - } - - if (!component.Dead) - return false; - - RemovePlant(plantholder, component); - AfterHarvest(plantholder, component); - return true; - } - - /// - /// Force do scream on PlantHolder (like plant is screaming) using seed's ScreamSound specifier (collection or soundPath) - /// - /// - public bool DoScream(EntityUid plantholder, SeedData? seed = null) - { - if (seed == null || seed.CanScream == false) - return false; - - _audio.PlayPvs(seed.ScreamSound, plantholder); - return true; - } - - public void AutoHarvest(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed == null || !component.Harvest) - return; - - _botany.AutoHarvest(component.Seed, Transform(uid).Coordinates); - AfterHarvest(uid, component); - } - - private void AfterHarvest(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Harvest = false; - component.LastProduce = component.Age; - - DoScream(uid, component.Seed); - - if (component.Seed?.HarvestRepeat == HarvestType.NoRepeat) - RemovePlant(uid, component); - - CheckLevelSanity(uid, component); - UpdateSprite(uid, component); - } - - public void CheckHealth(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Health <= 0) - { - Die(uid, component); - } - } - - public void Die(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Dead = true; - component.Harvest = false; - component.MutationLevel = 0; - component.YieldMod = 1; - component.MutationMod = 1; - component.ImproperLight = false; - component.ImproperHeat = false; - component.ImproperPressure = false; - component.WeedLevel += 1 * HydroponicsSpeedMultiplier; - component.PestLevel = 0; - UpdateSprite(uid, component); - } - public void RemovePlant(EntityUid uid, PlantHolderComponent? component = null) { if (!Resolve(uid, ref component)) return; - component.YieldMod = 1; - component.MutationMod = 1; component.PestLevel = 0; - component.Seed = null; - component.Dead = false; - component.Age = 0; - component.LastProduce = 0; - component.Sampled = false; - component.Harvest = false; - component.ImproperLight = false; component.ImproperPressure = false; component.ImproperHeat = false; - - UpdateSprite(uid, component); - } - - public void AffectGrowth(EntityUid uid, int amount, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed == null) - return; - - if (amount > 0) - { - if (component.Age < component.Seed.Maturation) - component.Age += amount; - else if (!component.Harvest && component.Seed.Yield <= 0f) - component.LastProduce -= amount; - } - else + if (component.PlantUid != null) { - if (component.Age < component.Seed.Maturation) - component.SkipAging++; - else if (!component.Harvest && component.Seed.Yield <= 0f) - component.LastProduce += amount; + GetPlant(component.PlantUid.Value, out var plant); + _plant.RemovePlant(plant); + component.PlantUid = null; } + + UpdateSprite(uid, component); } public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? component = null) @@ -824,7 +400,7 @@ public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? co if (!Resolve(uid, ref component)) return; - component.NutritionLevel += amount; + component.NutritionLevel = Math.Clamp(component.NutritionLevel + amount, 0, 100); } public void AdjustWater(EntityUid uid, float amount, PlantHolderComponent? component = null) @@ -832,12 +408,12 @@ public void AdjustWater(EntityUid uid, float amount, PlantHolderComponent? compo if (!Resolve(uid, ref component)) return; - component.WaterLevel += amount; + component.WaterLevel = Math.Clamp(component.WaterLevel + amount, 0, 100); // Water dilutes toxins. if (amount > 0) { - component.Toxins -= amount * 4f; + component.Toxins = Math.Clamp(component.Toxins - amount * 4f, 0, 100); } } @@ -849,7 +425,7 @@ public void UpdateReagents(EntityUid uid, PlantHolderComponent? component = null if (!_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution)) return; - if (solution.Volume > 0 && component.MutationLevel < 25) + if (solution.Volume > 0) { var amt = FixedPoint2.New(1); foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt)) @@ -858,25 +434,14 @@ public void UpdateReagents(EntityUid uid, PlantHolderComponent? component = null reagentProto.ReactionPlant(uid, entry, solution); } } - - CheckLevelSanity(uid, component); } - private void Mutate(EntityUid uid, float severity, PlantHolderComponent? component = null) + public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null) { if (!Resolve(uid, ref component)) return; - if (component.Seed != null) - { - EnsureUniqueSeed(uid, component); - _mutation.MutateSeed(uid, ref component.Seed, severity); - } - } - - public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) + if (!component.DrawWarnings) return; component.UpdateSpriteAfterUpdate = false; @@ -884,73 +449,59 @@ public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null) if (!TryComp(uid, out var app)) return; - if (component.Seed != null) + SeedData? seed = null; + + if (component.PlantUid != null) { - if (component.DrawWarnings) - { - _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= component.Seed.Endurance / 2f); - } + var hasPlant = GetPlant(component.PlantUid.Value, out var plantEnt); - if (component.Dead) + if (hasPlant && plantEnt.Comp != null) { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "dead", app); + seed = plantEnt.Comp.Seed; + _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, plantEnt.Comp.Harvest, app); } - else if (component.Harvest) + else { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "harvest", app); + _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, false, app); } - else if (component.Age < component.Seed.Maturation) - { - var growthStage = GetCurrentGrowthStage((uid, component)); - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{growthStage}", app); - component.LastProduce = component.Age; + if (seed == null || plantEnt.Comp == null) + { + _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app); } else { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{component.Seed.GrowthStages}", app); + _appearance.SetData(uid, PlantHolderVisuals.HealthLight, plantEnt.Comp.Health <= seed.Endurance / 2f); } } else { - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "", app); + _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, false, app); _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app); } - if (!component.DrawWarnings) - return; - _appearance.SetData(uid, PlantHolderVisuals.WaterLight, component.WaterLevel <= 15, app); _appearance.SetData(uid, PlantHolderVisuals.NutritionLight, component.NutritionLevel <= 8, app); _appearance.SetData(uid, PlantHolderVisuals.AlertLight, component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat || - component.ImproperLight || component.ImproperPressure || component.MissingGas > 0, app); - _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, component.Harvest, app); + component.ImproperPressure || component.MissingGas > 0, app); } - /// - /// Check if the currently contained seed is unique. If it is not, clone it so that we have a unique seed. - /// Necessary to avoid modifying global seeds. - /// - public void EnsureUniqueSeed(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed is { Unique: false }) - component.Seed = component.Seed.Clone(); - } public void ForceUpdateByExternalCause(EntityUid uid, PlantHolderComponent? component = null) { if (!Resolve(uid, ref component)) return; - component.SkipAging++; // We're forcing an update cycle, so one age hasn't passed. + if (component.PlantUid != null && GetPlant(component.PlantUid.Value, out var plant)) + { + plant.Comp.SkipAging++; // We're forcing an update cycle, so one age hasn't passed. + _plant.Update(component.PlantUid.Value, plant.Comp); + } + else + { + component.PlantUid = null; + } component.ForceUpdate = true; Update(uid, component); } diff --git a/Content.Server/Botany/Systems/PlantSystem.cs b/Content.Server/Botany/Systems/PlantSystem.cs new file mode 100644 index 00000000000000..81b2191b20fc35 --- /dev/null +++ b/Content.Server/Botany/Systems/PlantSystem.cs @@ -0,0 +1,631 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Server.Botany.Components; +using Content.Server.Kitchen.Components; +using Content.Server.Popups; +using Content.Shared.Atmos; +using Content.Shared.Botany; +using Content.Shared.Examine; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Content.Shared.Random; +using Content.Shared.Tag; +using Robust.Server.GameObjects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Botany.Systems; + +public sealed class PlantSystem : EntitySystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly MutationSystem _mutation = default!; + [Dependency] private readonly RandomHelperSystem _randomHelper = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly BotanySystem _botany = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public const float HydroponicsSpeedMultiplier = 1f; + public const float HydroponicsConsumptionMultiplier = 2f; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var plant)) + { + if (plant.NextUpdate > _gameTiming.CurTime) + continue; + plant.NextUpdate = _gameTiming.CurTime + plant.UpdateDelay; + + Update(uid, plant); + } + } + + private void OnExamine(Entity entity, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var plant = entity.Comp; + + using (args.PushGroup(nameof(PlantComponent))) + { + var (_, component) = entity; + SeedData? seed = plant.Seed; + if (seed == null) + return; + + if (!plant.Dead) + { + var displayName = Loc.GetString(seed.DisplayName); + args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", + ("seedName", displayName), + ("toBeForm", displayName.EndsWith('s') ? "are" : "is"))); + + if (plant.Health <= seed.Endurance / 2) + { + args.PushMarkup(Loc.GetString( + "plant-holder-component-something-already-growing-low-health-message", + ("healthState", + Loc.GetString(plant.Age > seed.Lifespan + ? "plant-holder-component-plant-old-adjective" + : "plant-holder-component-plant-unhealthy-adjective")))); + } + } + else + { + args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message")); + } + } + } + + private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) + { + GetEverything(entity, out var plant, out var seed, out var holder); + + if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) + { + args.Handled = true; + if (plant == null || seed == null) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User); + return; + } + + if (plant.Sampled) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User); + return; + } + + if (plant.Dead) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User); + return; + } + + if (GetCurrentGrowthStage(entity) <= 1) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-early-sample-message"), args.User); + return; + } + + plant.Health -= (_random.Next(3, 5) * 10); + + float? healthOverride; + if (plant.Harvest) + { + healthOverride = null; + } + else + { + healthOverride = plant.Health; + } + var packetSeed = seed; + var seedItem = _botany.SpawnSeedPacket(packetSeed, Transform(args.User).Coordinates, args.User, healthOverride); + _randomHelper.RandomOffset(seedItem, 0.25f); + var displayName = Loc.GetString(seed.DisplayName); + _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", + ("seedName", displayName)), args.User); + + DoScream(entity, seed); + + if (_random.Prob(0.3f)) + plant.Sampled = true; + + if (plant.PlantHolderUid != null) + _plantHolder.UpdateSprite(plant.PlantHolderUid.Value, holder); + return; + } + + if (HasComp(args.Used)) + { + DoHarvest(entity, args.User); + args.Handled = true; + } + } + + private void OnInteractHand(Entity entity, ref InteractHandEvent args) + { + args.Handled = true; + DoHarvest(entity, args.User); + } + + /// + /// A player has clicked on a harvestable plant. Make its produce and update tracking. + /// + public bool DoHarvest(EntityUid plantEntity, EntityUid user) + { + GetEverything(plantEntity, out var plant, out var seed, out var holder); + if (Deleted(user) || plant == null || seed == null || holder == null) + return false; + + if (plant.Dead) + { + RemovePlant(plantEntity); + return true; + } + + if (!plant.Harvest) + return false; + + if (TryComp(user, out var hands)) + { + if (!_botany.CanHarvest(seed, hands.ActiveHandEntity)) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user); + return false; + } + } + else if (!_botany.CanHarvest(seed)) + { + return false; + } + + _botany.Harvest(seed, user); + AfterHarvest(plantEntity, plant); + return true; + } + + /// + /// Delete this plant and remove it from its plantHolder. + /// + public void RemovePlant(EntityUid uid) + { + GetEverything(uid, out var plant, out _, out var holder); + + QueueDel(uid); + if (holder != null && plant != null && plant.PlantHolderUid != null) + { + holder.PlantUid = null; + _plantHolder.ForceUpdateByExternalCause(plant.PlantHolderUid.Value); + } + } + + private void AfterHarvest(EntityUid uid, PlantComponent? component = null) + { + GetEverything(uid, out var plant, out var seed, out var holder); + + if (plant == null || seed == null || holder == null) + return; + + plant.Harvest = false; + plant.LastProduce = plant.Age; + + DoScream(uid, seed); + + if (seed.HarvestRepeat == HarvestType.NoRepeat) + { + RemovePlant(uid); + } + else + UpdateSprite(uid, plant); + } + + /// + /// The plant is harvesting itself on a growth tick. + /// + public void AutoHarvest(EntityUid uid, PlantComponent? component = null) + { + GetEverything(uid, out component, out var seed, out _); + if (component == null || seed == null) + return; + + if (seed == null || !component.Harvest) + return; + + _botany.AutoHarvest(seed, Transform(uid).Coordinates); + AfterHarvest(uid, component); + } + + /// + /// AAAAAAAAAAAAAHHHHHHHHHHH! + /// + public bool DoScream(EntityUid plant, SeedData? seed = null) + { + if (seed == null || seed.CanScream == false) + return false; + + _audio.PlayPvs(seed.ScreamSound, plant); + return true; + } + + public void Update(EntityUid plantuid, PlantComponent? plant = null) + { + if (!GetEverything(plantuid, out plant, out var seed, out var holder) || plant == null || seed == null || holder == null) + return; + + plant.Health = MathHelper.Clamp(plant.Health, 0, seed.Endurance); + // Process mutations + if (plant.MutationLevel > 0) + { + Mutate(plantuid, Math.Min(plant.MutationLevel, 25)); + plant.MutationLevel = 0; + } + + // Advance plant age here. + if (plant.SkipAging > 0) + plant.SkipAging--; + else + { + if (_random.Prob(0.8f)) + plant.Age += (int)(1 * HydroponicsSpeedMultiplier); + } + + // Nutrient consumption. + if (seed.NutrientConsumption > 0 && holder.NutritionLevel > 0 && _random.Prob(0.75f)) + { + holder.NutritionLevel -= MathF.Max(0f, seed.NutrientConsumption * HydroponicsSpeedMultiplier); + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + // Water consumption. + if (seed.WaterConsumption > 0 && holder.WaterLevel > 0 && _random.Prob(0.75f)) + { + holder.WaterLevel -= MathF.Max(0f, + seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier); + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier; + + // Make sure genetics are viable. + if (!seed.Viable) + { + AffectGrowth(plantuid, -1, plant); + plant.Health -= 6 * healthMod; + } + + // Prevents the plant from aging when lacking resources. + // Limits the effect on aging so that when resources are added, the plant starts growing in a reasonable amount of time. + if (plant.SkipAging < 10) + { + // Make sure the plant is not starving. + if (holder.NutritionLevel > 5) + { + plant.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod; + } + else + { + AffectGrowth(plantuid, -1, plant); + plant.Health -= healthMod; + } + + // Make sure the plant is not thirsty. + if (holder.WaterLevel > 10) + { + plant.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod; + } + else + { + AffectGrowth(plantuid, -1, plant); + plant.Health -= healthMod; + } + + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + var environment = _atmosphere.GetContainingMixture(plantuid, true, true) ?? GasMixture.SpaceGas; + + holder.MissingGas = 0; + if (seed.ConsumeGasses.Count > 0) + { + foreach (var (gas, amount) in seed.ConsumeGasses) + { + if (environment.GetMoles(gas) < amount) + { + holder.MissingGas++; + continue; + } + + environment.AdjustMoles(gas, -amount); + } + + if (holder.MissingGas > 0) + { + plant.Health -= holder.MissingGas * HydroponicsSpeedMultiplier; + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + } + + // SeedPrototype pressure resistance. + var pressure = environment.Pressure; + if (pressure < seed.LowPressureTolerance || pressure > seed.HighPressureTolerance) + { + plant.Health -= healthMod; + holder.ImproperPressure = true; + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + else + { + holder.ImproperPressure = false; + } + + // SeedPrototype ideal temperature. + if (MathF.Abs(environment.Temperature - seed.IdealHeat) > seed.HeatTolerance) + { + plant.Health -= healthMod; + holder.ImproperHeat = true; + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + else + { + holder.ImproperHeat = false; + } + + // Gas production. + var exudeCount = seed.ExudeGasses.Count; + if (exudeCount > 0) + { + foreach (var (gas, amount) in seed.ExudeGasses) + { + environment.AdjustMoles(gas, + MathF.Max(1f, MathF.Round(amount * MathF.Round(seed.Potency) / exudeCount))); + } + } + + // Toxin levels beyond the plant's tolerance cause damage. + // They are, however, slowly reduced over time. + if (holder.Toxins > 0) + { + var toxinUptake = MathF.Max(1, MathF.Round(holder.Toxins / 10f)); + if (holder.Toxins > seed.ToxinsTolerance) + { + plant.Health -= toxinUptake; + } + + holder.Toxins -= toxinUptake; + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + // Pest levels. + if (holder.PestLevel > 0) + { + if (holder.PestLevel > seed.PestTolerance) + { + plant.Health -= HydroponicsSpeedMultiplier; + } + + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + // Weed levels. + if (holder.WeedLevel > 0) + { + if (holder.WeedLevel >= seed.WeedTolerance) + { + plant.Health -= HydroponicsSpeedMultiplier; + } + + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + + if (plant.Age > seed.Lifespan) + { + plant.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier; + if (holder.DrawWarnings) + holder.UpdateSpriteAfterUpdate = true; + } + else if (plant.Age < 0 && plant.PlantHolderUid != null) // Revert back to seed packet! + { + var packetSeed = seed; + // will put it in the trays hands if it has any, please do not try doing this + if (plant.PlantHolderUid != null) + _botany.SpawnSeedPacket(packetSeed, Transform(plant.PlantHolderUid.Value).Coordinates, plant.PlantHolderUid.Value); + RemovePlant(plantuid); + holder.ForceUpdate = true; + if (plant.PlantHolderUid != null) + _plantHolder.Update(plant.PlantHolderUid.Value, holder); + return; + } + + if (plant.Health <= 0) + Die(plantuid); + + if (plant.Harvest && seed.HarvestRepeat == HarvestType.SelfHarvest) + AutoHarvest(plantuid, plant); + + // If enough time has passed since the plant was harvested, we're ready to harvest again! + if (!plant.Dead && seed.ProductPrototypes.Count > 0) + { + if (plant.Age > seed.Production) + { + if (plant.Age - plant.LastProduce > seed.Production && !plant.Harvest) + { + plant.Harvest = true; + plant.LastProduce = plant.Age; + } + } + else + { + if (plant.Harvest) + { + plant.Harvest = false; + plant.LastProduce = plant.Age; + } + } + } + + UpdateSprite(plantuid, plant); + } + + /// + /// Gets all the components containing data used by the plant: Plant, its SeedData for convenience, and its PlantHolder. + /// Returns false if any of them weren't found, but components that were present will be available. + /// + public bool GetEverything(EntityUid plantEntity, out PlantComponent? plant, out SeedData? seed, out PlantHolderComponent? holder) + { + seed = null; + holder = null; + bool plantFound = TryComp(plantEntity, out plant); + bool holderFound = false; + if (plant != null) + { + seed = plant.Seed; + holderFound = TryComp(plant.PlantHolderUid, out holder); + } + return plantFound && seed != null && holderFound; + } + + /// + /// Cease the growth and harvesting of a plant through the conclusion of biological processes. + /// + public void Die(EntityUid uid, PlantComponent? plant = null) + { + if (!GetEverything(uid, out plant, out var seed, out var holder) || plant == null) + return; + + plant.Dead = true; + plant.Harvest = false; + plant.MutationLevel = 0; + UpdateSprite(uid, plant); + + if (holder != null) + { + holder.ImproperHeat = false; + holder.ImproperPressure = false; + holder.WeedLevel = Math.Clamp(holder.WeedLevel + (1 * HydroponicsSpeedMultiplier), 0f, 10f); + holder.PestLevel = 0; + if (plant.PlantHolderUid != null) + _plantHolder.UpdateSprite(plant.PlantHolderUid.Value, holder); + } + } + + private int GetCurrentGrowthStage(Entity entity) + { + GetEverything(entity, out var plant, out _, out _); + if (plant == null || plant.Seed == null) + return 0; + var seed = plant.Seed; + + var result = Math.Max(1, (int)(plant.Age * seed.GrowthStages / seed.Maturation)); + return result; + } + + /// + /// Adjusts the aging of a plant. Positive amounts make plant older (and harvests soon), negative amounts make the plant delay growth + /// (and slows down harvests) + /// + public void AffectGrowth(EntityUid uid, int amount, PlantComponent? component = null) + { + if (!GetEverything(uid, out component, out var seed, out var _) || component == null || seed == null) + return; + + if (amount > 0) + { + if (component.Age < seed.Maturation) + component.Age += amount; + else if (!component.Harvest && seed.Yield <= 0f) + component.LastProduce -= amount; + } + else + { + if (component.Age < seed.Maturation) + component.SkipAging++; + else if (!component.Harvest && seed.Yield <= 0f) + component.LastProduce += amount; + } + } + + private void Mutate(EntityUid uid, float severity) + { + GetEverything(uid, out _, out var seed, out _); + + if (seed != null) + { + _mutation.CheckRandomMutations(uid, ref seed, severity); + } + } + + /// + /// Watch your plant grow. + /// + public void UpdateSprite(EntityUid uid, PlantComponent? component = null) + { + GetEverything(uid, out var plant, out _, out _); + if (plant == null) + return; + var seed = plant.Seed; + if (seed == null) + return; + + if (!Resolve(uid, ref component)) + return; + + if (!TryComp(uid, out var app)) + return; + + if (seed == null) + return; + + if (component.Dead) + { + _appearance.SetData(uid, PlantVisuals.PlantRsi, seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantVisuals.PlantState, "dead", app); + } + else if (component.Harvest) + { + _appearance.SetData(uid, PlantVisuals.PlantRsi, seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantVisuals.PlantState, "harvest", app); + } + else if (component.Age < seed.Maturation) + { + var growthStage = GetCurrentGrowthStage((uid, component)); + + _appearance.SetData(uid, PlantVisuals.PlantRsi, seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantVisuals.PlantState, $"stage-{growthStage}", app); + component.LastProduce = component.Age; + } + else + { + _appearance.SetData(uid, PlantVisuals.PlantRsi, seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantVisuals.PlantState, $"stage-{seed.GrowthStages}", app); + } + + Dirty(uid, app); + } +} diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index 93f76473ff886a..c13fe687d224f3 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -27,6 +27,8 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor if (!TryComp(args.Used, out ProduceComponent? produce)) return; + + args.Handled = true; if (!_botanySystem.TryGetSeed(produce, out var seed) || seed.Seedless) { _popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-no-seeds", ("name", args.Used)), diff --git a/Content.Server/EntityEffects/Effects/PlantChangeStat.cs b/Content.Server/EntityEffects/Effects/PlantChangeStat.cs index 9592ff779daa8b..8dcbc7ffa6485c 100644 --- a/Content.Server/EntityEffects/Effects/PlantChangeStat.cs +++ b/Content.Server/EntityEffects/Effects/PlantChangeStat.cs @@ -24,20 +24,20 @@ public sealed partial class PlantChangeStat : EntityEffect public override void Effect(EntityEffectBaseArgs args) { - var plantHolder = args.EntityManager.GetComponent(args.TargetEntity); - if (plantHolder == null || plantHolder.Seed == null) + var plant = args.EntityManager.GetComponent(args.TargetEntity); + if (plant == null || plant.Seed == null) return; - var member = plantHolder.Seed.GetType().GetField(TargetValue); + var member = plant.Seed.GetType().GetField(TargetValue); var mutationSys = args.EntityManager.System(); if (member == null) { - mutationSys.Log.Error(this.GetType().Name + " Error: Member " + TargetValue + " not found on " + plantHolder.GetType().Name + ". Did you misspell it?"); + mutationSys.Log.Error(this.GetType().Name + " Error: Member " + TargetValue + " not found on " + plant.GetType().Name + ". Did you misspell it?"); return; } - var currentValObj = member.GetValue(plantHolder.Seed); + var currentValObj = member.GetValue(plant.Seed); if (currentValObj == null) return; @@ -45,19 +45,19 @@ public override void Effect(EntityEffectBaseArgs args) { var floatVal = (float)currentValObj; MutateFloat(ref floatVal, MinValue, MaxValue, Steps); - member.SetValue(plantHolder.Seed, floatVal); + member.SetValue(plant.Seed, floatVal); } else if (member.FieldType == typeof(int)) { var intVal = (int)currentValObj; MutateInt(ref intVal, (int)MinValue, (int)MaxValue, Steps); - member.SetValue(plantHolder.Seed, intVal); + member.SetValue(plant.Seed, intVal); } else if (member.FieldType == typeof(bool)) { var boolVal = (bool)currentValObj; boolVal = !boolVal; - member.SetValue(plantHolder.Seed, boolVal); + member.SetValue(plant.Seed, boolVal); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs index 5133fe502ffc8f..128a49b20783f0 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs @@ -1,7 +1,6 @@ using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using Robust.Shared.Prototypes; -using Robust.Shared.Random; using System.Diagnostics.CodeAnalysis; namespace Content.Server.EntityEffects.Effects.PlantMetabolism; @@ -32,17 +31,18 @@ public abstract partial class PlantAdjustAttribute : EntityEffect /// The entity manager /// Whether to check if it has an alive plant or not /// - public bool CanMetabolize(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent, + public bool CanMetabolize(EntityUid plant, [NotNullWhen(true)] out PlantComponent? plantComponent, IEntityManager entityManager, bool mustHaveAlivePlant = true) { - plantHolderComponent = null; - - if (!entityManager.TryGetComponent(plantHolder, out plantHolderComponent) - || mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead)) - return false; - - return true; + if (entityManager.TryGetComponent(plant, out plantComponent)) + { + return mustHaveAlivePlant ? !plantComponent.Dead : true; //a plant exists, check if we need it alive. + } + else + { + return !mustHaveAlivePlant; // There is no plant, we must not require one. + } } protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs index 774f6ec48c6735..9863bb33b2a9bb 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; @@ -6,16 +7,18 @@ namespace Content.Server.EntityEffects.Effects.PlantMetabolism; public sealed partial class PlantAdjustHealth : PlantAdjustAttribute { public override string GuidebookAttributeName { get; set; } = "plant-attribute-health"; - + public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager)) return; - var plantHolder = args.EntityManager.System(); - - plantHolderComp.Health += Amount; - plantHolder.CheckHealth(args.TargetEntity, plantHolderComp); + plantComp.Health += Amount; + if (plantComp.Health <= 0) + { + var plant = args.EntityManager.System(); + plant.Die(args.TargetEntity); + } } } - diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs index bf41a21bcb0d64..b84ff5144515a2 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; namespace Content.Server.EntityEffects.Effects.PlantMetabolism; @@ -8,9 +9,10 @@ public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager)) return; - plantHolderComp.MutationLevel += Amount * plantHolderComp.MutationMod; + plantComp.MutationLevel += Amount * plantComp.MutationMod; } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs index 6ed793e61431f6..c626001f3ecb7c 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -10,10 +11,11 @@ public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager)) return; - plantHolderComp.MutationMod += Amount; + plantComp.MutationMod = Math.Clamp(plantComp.MutationMod + Amount, 1f, 3f); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs index b9389afacdf032..63bd111a090ff7 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,11 +12,7 @@ public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false)) - return; - var plantHolder = args.EntityManager.System(); - - plantHolder.AdjustNutrient(args.TargetEntity, Amount, plantHolderComp); + plantHolder.AdjustNutrient(args.TargetEntity, Amount); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs index 219529bfc4cf9b..c6f3813630440d 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,10 +12,11 @@ public sealed partial class PlantAdjustPests : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager)) return; - plantHolderComp.PestLevel += Amount; + plantHolderComp.PestLevel = Math.Clamp(plantHolderComp.PestLevel + Amount, 0, 10); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs index 5776463ce718a1..003c267a16bf91 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs @@ -1,4 +1,4 @@ -using Content.Server.Botany.Systems; +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; namespace Content.Server.EntityEffects.Effects.PlantMetabolism; @@ -13,16 +13,12 @@ public sealed partial class PlantAdjustPotency : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null + || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager) + || plantComp.Seed == null || plantComp.Seed.Immutable) return; - if (plantHolderComp.Seed == null) - return; - - var plantHolder = args.EntityManager.System(); - - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); - - plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + Amount, 1); + plantComp.Seed.Potency = Math.Max(plantComp.Seed.Potency + Amount, 1); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs index ab1baa42850c5e..ba5097fe968b4a 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,9 +12,7 @@ public sealed partial class PlantAdjustToxins : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) - return; - + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); plantHolderComp.Toxins += Amount; } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs index 41774977d54301..f7f671dd2f772e 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,12 +12,8 @@ public sealed partial class PlantAdjustWater : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false)) - return; - var plantHolder = args.EntityManager.System(); - - plantHolder.AdjustWater(args.TargetEntity, Amount, plantHolderComp); + plantHolder.AdjustWater(args.TargetEntity, Amount); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs index 421e31998db0d9..3d6584fac2b716 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,9 +12,9 @@ public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp)) return; - plantHolderComp.WeedLevel += Amount; + plantHolderComp.WeedLevel = Math.Clamp(plantHolderComp.WeedLevel + Amount, 0f, 10f); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs index 397eace399af4f..29e5169524af97 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs @@ -1,3 +1,4 @@ +using Content.Server.Botany.Components; using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -11,12 +12,12 @@ public sealed partial class PlantAffectGrowth : PlantAdjustAttribute public override void Effect(EntityEffectBaseArgs args) { - if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager)) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null || !CanMetabolize(plantHolderComp.PlantUid.Value, out PlantComponent? plantComp, args.EntityManager)) return; - var plantHolder = args.EntityManager.System(); - - plantHolder.AffectGrowth(args.TargetEntity, (int) Amount, plantHolderComp); + var plant = args.EntityManager.System(); + plant.AffectGrowth(plantHolderComp.PlantUid.Value, (int)Amount, plantComp); } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs index 7fe4b7a1707b16..475f0e544d0898 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs @@ -1,4 +1,5 @@ using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; using Robust.Shared.Prototypes; @@ -12,20 +13,24 @@ public sealed partial class PlantCryoxadone : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null || plantHolderComp.Dead) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null) return; var deviation = 0; - var seed = plantHolderComp.Seed; + var seed = plantComp.Seed; var random = IoCManager.Resolve(); - if (plantHolderComp.Age > seed.Maturation) - deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - random.Next(7, 10)); + if (plantComp.Age > seed.Maturation) + deviation = (int)Math.Max(seed.Maturation - 1, plantComp.Age - random.Next(7, 10)); else - deviation = (int) (seed.Maturation / seed.GrowthStages); - plantHolderComp.Age -= deviation; - plantHolderComp.SkipAging++; - plantHolderComp.ForceUpdate = true; + deviation = (int)(seed.Maturation / seed.GrowthStages); + plantComp.Age -= deviation; + plantComp.SkipAging++; + var plantSys = args.EntityManager.System(); + plantSys.Update(plantHolderComp.PlantUid.Value, plantComp); } protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability)); diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs index 2929bb6ee9e161..938c89234bd389 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs @@ -1,5 +1,4 @@ -using Content.Server.Botany.Components; -using Content.Server.Botany.Systems; +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using Content.Shared.Popups; using Robust.Shared.Prototypes; @@ -14,26 +13,22 @@ public sealed partial class PlantDestroySeeds : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - if ( - !args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null - || plantHolderComp.Dead - || plantHolderComp.Seed.Immutable - ) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null || plantComp.Seed.Immutable) return; - var plantHolder = args.EntityManager.System(); - var popupSystem = args.EntityManager.System(); - - if (plantHolderComp.Seed.Seedless == false) + if (plantComp.Seed != null && plantComp.Seed.Seedless == false) { - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); + var popupSystem = args.EntityManager.System(); popupSystem.PopupEntity( Loc.GetString("botany-plant-seedsdestroyed"), args.TargetEntity, PopupType.SmallCaution ); - plantHolderComp.Seed.Seedless = true; + plantComp.Seed.Seedless = true; } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs index 36b6a8334235ae..7509cc46fc0228 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs @@ -1,5 +1,4 @@ using Content.Server.Botany.Components; -using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; using Robust.Shared.Prototypes; @@ -13,29 +12,25 @@ public sealed partial class PlantDiethylamine : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null || plantHolderComp.Dead || - plantHolderComp.Seed.Immutable) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null || plantComp.Seed.Immutable) return; - - - var plantHolder = args.EntityManager.System(); var random = IoCManager.Resolve(); if (random.Prob(0.1f)) { - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Lifespan++; + plantComp.Seed.Lifespan++; } if (random.Prob(0.1f)) { - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Endurance++; + plantComp.Seed.Endurance++; } } - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability)); } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs index 96d98bfbf2ec66..90ec224137ebab 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs @@ -11,12 +11,14 @@ public sealed partial class PlantPhalanximine : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null || plantHolderComp.Dead || - plantHolderComp.Seed.Immutable) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null || plantComp.Seed.Immutable) return; - plantHolderComp.Seed.Viable = true; + plantComp.Seed.Viable = true; } protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability)); diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs index 11af8d511fe7a5..cf1d0612737913 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs @@ -1,5 +1,4 @@ -using Content.Server.Botany.Components; -using Content.Server.Botany.Systems; +using Content.Server.Botany.Components; using Content.Shared.EntityEffects; using Content.Shared.Popups; using Robust.Shared.Prototypes; @@ -7,29 +6,25 @@ namespace Content.Server.EntityEffects.Effects.PlantMetabolism; /// -/// Handles restoral of seeds on a plant. +/// Handles restoration of seeds on a plant. /// public sealed partial class PlantRestoreSeeds : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - if ( - !args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null - || plantHolderComp.Dead - || plantHolderComp.Seed.Immutable - ) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null || plantComp.Seed.Immutable) return; - var plantHolder = args.EntityManager.System(); - var popupSystem = args.EntityManager.System(); - - if (plantHolderComp.Seed.Seedless) + if (plantComp.Seed.Seedless) { - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); + var popupSystem = args.EntityManager.System(); popupSystem.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.TargetEntity); - plantHolderComp.Seed.Seedless = false; + plantComp.Seed.Seedless = false; } } diff --git a/Content.Server/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs b/Content.Server/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs index 695cb96675c5f3..f4298e6c760ebe 100644 --- a/Content.Server/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs +++ b/Content.Server/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs @@ -1,5 +1,4 @@ using Content.Server.Botany.Components; -using Content.Server.Botany.Systems; using Content.Shared.EntityEffects; using JetBrains.Annotations; using Robust.Shared.Prototypes; @@ -22,30 +21,28 @@ public sealed partial class RobustHarvest : EntityEffect public override void Effect(EntityEffectBaseArgs args) { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp) - || plantHolderComp.Seed == null || plantHolderComp.Dead || - plantHolderComp.Seed.Immutable) + var plantHolderComp = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolderComp.PlantUid == null) + return; + var plantComp = args.EntityManager.GetComponent(plantHolderComp.PlantUid.Value); + if (plantComp == null || plantComp.Dead || plantComp.Seed == null || plantComp.Seed.Immutable) return; - - var plantHolder = args.EntityManager.System(); var random = IoCManager.Resolve(); - if (plantHolderComp.Seed.Potency < PotencyLimit) + if (plantComp.Seed.Potency < PotencyLimit) { - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + PotencyIncrease, PotencyLimit); + plantComp.Seed.Potency = Math.Min(plantComp.Seed.Potency + PotencyIncrease, PotencyLimit); - if (plantHolderComp.Seed.Potency > PotencySeedlessThreshold) + if (plantComp.Seed.Potency > PotencySeedlessThreshold) { - plantHolderComp.Seed.Seedless = true; + plantComp.Seed.Seedless = true; } } - else if (plantHolderComp.Seed.Yield > 1 && random.Prob(0.1f)) + else if (plantComp.Seed.Yield > 1 && random.Prob(0.1f)) { // Too much of a good thing reduces yield - plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Yield--; + plantComp.Seed.Yield--; } } diff --git a/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs b/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs index 7ee6cd13d758c2..546a157cf6de63 100644 --- a/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs +++ b/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs @@ -14,14 +14,14 @@ public sealed partial class PlantMutateChemicals : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + var plant = args.EntityManager.GetComponent(args.TargetEntity); - if (plantholder.Seed == null) + if (plant.Seed == null) return; var random = IoCManager.Resolve(); var prototypeManager = IoCManager.Resolve(); - var chemicals = plantholder.Seed.Chemicals; + var chemicals = plant.Seed.Chemicals; var randomChems = prototypeManager.Index("RandomPickBotanyReagent").Fills; // Add a random amount of a random chemical to this set of chemicals diff --git a/Content.Server/EntityEffects/Effects/PlantMutateGases.cs b/Content.Server/EntityEffects/Effects/PlantMutateGases.cs index 52b9da3a851512..1940990dd4b6bd 100644 --- a/Content.Server/EntityEffects/Effects/PlantMutateGases.cs +++ b/Content.Server/EntityEffects/Effects/PlantMutateGases.cs @@ -20,13 +20,13 @@ public sealed partial class PlantMutateExudeGasses : EntityEffect public override void Effect(EntityEffectBaseArgs args) { - var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + var plant = args.EntityManager.GetComponent(args.TargetEntity); - if (plantholder.Seed == null) + if (plant.Seed == null) return; var random = IoCManager.Resolve(); - var gasses = plantholder.Seed.ExudeGasses; + var gasses = plant.Seed.ExudeGasses; // Add a random amount of a random gas to this gas dictionary float amount = random.NextFloat(MinValue, MaxValue); @@ -59,13 +59,13 @@ public sealed partial class PlantMutateConsumeGasses : EntityEffect public float MaxValue = 0.5f; public override void Effect(EntityEffectBaseArgs args) { - var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + var plant = args.EntityManager.GetComponent(args.TargetEntity); - if (plantholder.Seed == null) + if (plant.Seed == null) return; var random = IoCManager.Resolve(); - var gasses = plantholder.Seed.ConsumeGasses; + var gasses = plant.Seed.ConsumeGasses; // Add a random amount of a random gas to this gas dictionary float amount = random.NextFloat(MinValue, MaxValue); diff --git a/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs b/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs index e67176ee16db5e..88a724cab2cc06 100644 --- a/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs +++ b/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs @@ -12,15 +12,15 @@ public sealed partial class PlantMutateHarvest : EntityEffect { public override void Effect(EntityEffectBaseArgs args) { - var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + var plant = args.EntityManager.GetComponent(args.TargetEntity); - if (plantholder.Seed == null) + if (plant.Seed == null) return; - if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat) - plantholder.Seed.HarvestRepeat = HarvestType.Repeat; - else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat) - plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest; + if (plant.Seed.HarvestRepeat == HarvestType.NoRepeat) + plant.Seed.HarvestRepeat = HarvestType.Repeat; + else if (plant.Seed.HarvestRepeat == HarvestType.Repeat) + plant.Seed.HarvestRepeat = HarvestType.SelfHarvest; } protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) diff --git a/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs b/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs index 65bd59daa37068..dc0f9a8f2f31dc 100644 --- a/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs +++ b/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs @@ -15,16 +15,13 @@ public sealed partial class PlantSpeciesChange : EntityEffect public override void Effect(EntityEffectBaseArgs args) { var prototypeManager = IoCManager.Resolve(); - var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + var plant = args.EntityManager.GetComponent(args.TargetEntity); - if (plantholder.Seed == null) - return; - - if (plantholder.Seed.MutationPrototypes.Count == 0) + if (plant.Seed == null || plant.Seed.MutationPrototypes.Count == 0) return; var random = IoCManager.Resolve(); - var targetProto = random.Pick(plantholder.Seed.MutationPrototypes); + var targetProto = random.Pick(plant.Seed.MutationPrototypes); prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed); if (protoSeed == null) @@ -32,8 +29,7 @@ public override void Effect(EntityEffectBaseArgs args) Log.Error($"Seed prototype could not be found: {targetProto}!"); return; } - - plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed); + plant.Seed = plant.Seed.SpeciesChange(protoSeed); } protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) diff --git a/Content.Shared/Botany/PlantHolderVisuals.cs b/Content.Shared/Botany/PlantHolderVisuals.cs index 76ea387d3db153..c6954acb88b18a 100644 --- a/Content.Shared/Botany/PlantHolderVisuals.cs +++ b/Content.Shared/Botany/PlantHolderVisuals.cs @@ -1,16 +1,21 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Serialization; namespace Content.Shared.Botany { [Serializable, NetSerializable] public enum PlantHolderVisuals { - PlantRsi, - PlantState, HealthLight, WaterLight, NutritionLight, AlertLight, HarvestLight, } + + [Serializable, NetSerializable] + public enum PlantVisuals + { + PlantRsi, + PlantState, + } } diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index dba2ba03a3f060..3b46afde5078fe 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -1,12 +1,9 @@ -using System.Collections.Frozen; -using System.Linq; -using System.Text.Json.Serialization; using Content.Shared.Administration.Logs; using Content.Shared.Body.Prototypes; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; -using Content.Shared.EntityEffects; using Content.Shared.Database; +using Content.Shared.EntityEffects; using Content.Shared.FixedPoint; using Content.Shared.Nutrition; using Robust.Shared.Audio; @@ -14,9 +11,11 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; using Robust.Shared.Utility; +using System.Collections.Frozen; +using System.Linq; +using System.Text.Json.Serialization; namespace Content.Shared.Chemistry.Reagent { @@ -164,14 +163,11 @@ public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityMa return removed; } - public void ReactionPlant(EntityUid? plantHolder, ReagentQuantity amount, Solution solution) + public void ReactionPlant(EntityUid plantHolder, ReagentQuantity amount, Solution solution) { - if (plantHolder == null) - return; - var entMan = IoCManager.Resolve(); var random = IoCManager.Resolve(); - var args = new EntityEffectReagentArgs(plantHolder.Value, entMan, null, solution, amount.Quantity, this, null, 1f); + var args = new EntityEffectReagentArgs(plantHolder, entMan, null, solution, amount.Quantity, this, null, 1f); foreach (var plantMetabolizable in PlantMetabolisms) { if (!plantMetabolizable.ShouldApply(args, random)) diff --git a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl index 0f416455c7eb5e..ec5b037ab0cfe5 100644 --- a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl +++ b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl @@ -31,4 +31,4 @@ plant-holder-component-heat-improper-warning = The [color=orange]improper temper plant-holder-component-pressure-improper-warning = The [color=lightblue]improper environment pressure alert[/color] is blinking. plant-holder-component-gas-missing-warning = The [color=cyan]improper gas environment alert[/color] is blinking. plant-holder-component-early-sample-message = The plant hasn't grown enough to take a sample yet. -plant-holder-component-ligneous-cant-harvest-message = The plant is too tough to harvest with your bare hands. +plant-holder-component-ligneous-cant-harvest-message = The plant is too tough to harvest with your bare hands. \ No newline at end of file diff --git a/Resources/Maps/Salvage/vegan-meatball.yml b/Resources/Maps/Salvage/vegan-meatball.yml index 824e3d0b7ea5e4..85a4bdcd9ea856 100644 --- a/Resources/Maps/Salvage/vegan-meatball.yml +++ b/Resources/Maps/Salvage/vegan-meatball.yml @@ -494,8 +494,6 @@ entities: highPressureTolerance: 121 lowPressureTolerance: 81 toxinsTolerance: 4 - lightTolerance: 3 - idealLight: 9 heatTolerance: 10 idealHeat: 298 waterConsumption: 0.4 @@ -567,8 +565,6 @@ entities: highPressureTolerance: 121 lowPressureTolerance: 81 toxinsTolerance: 4 - lightTolerance: 3 - idealLight: 7 heatTolerance: 10 idealHeat: 293 waterConsumption: 0.6 diff --git a/Resources/Prototypes/Hydroponics/plants.yml b/Resources/Prototypes/Hydroponics/plants.yml new file mode 100644 index 00000000000000..5cc00652f1b4d1 --- /dev/null +++ b/Resources/Prototypes/Hydroponics/plants.yml @@ -0,0 +1,20 @@ +- type: entity + id: BasePlant + components: + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.5 + density: 190 + hard: true + layer: + - BulletImpassable + - type: Sprite + - type: Plant + - type: Appearance + - type: PlantVisuals + - type: Clickable + - type: InteractionOutline + - type: Physics \ No newline at end of file diff --git a/Resources/Prototypes/Hydroponics/randomMutations.yml b/Resources/Prototypes/Hydroponics/randomMutations.yml index f203f8454730fc..976d726ee6832e 100644 --- a/Resources/Prototypes/Hydroponics/randomMutations.yml +++ b/Resources/Prototypes/Hydroponics/randomMutations.yml @@ -17,7 +17,6 @@ effect: !type:Slipify - name: ChangeSpecies baseOdds: 0.036 - appliesToProduce: false effect: !type:PlantSpeciesChange persists: false - name: Unviable diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml index 7349f3c3d72412..d09f5698c2ae2c 100644 --- a/Resources/Prototypes/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Hydroponics/seeds.yml @@ -14,7 +14,6 @@ production: 3 yield: 3 potency: 5 - idealLight: 8 nutrientConsumption: 0.40 chemicals: Nutriment: @@ -40,7 +39,6 @@ production: 3 yield: 3 potency: 5 - idealLight: 8 nutrientConsumption: 0.40 chemicals: Nutriment: @@ -66,7 +64,6 @@ production: 3 yield: 3 potency: 5 - idealLight: 8 nutrientConsumption: 0.40 chemicals: Nutriment: @@ -94,7 +91,6 @@ maturation: 6 production: 6 yield: 2 - idealLight: 9 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -121,7 +117,6 @@ maturation: 6 production: 6 yield: 2 - idealLight: 9 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -181,7 +176,6 @@ production: 5 yield: 3 potency: 20 - idealLight: 8 harvestRepeat: Repeat nutrientConsumption: 0.6 waterConsumption: 0.6 @@ -218,7 +212,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 8 chemicals: Nutriment: Min: 1 @@ -244,7 +237,6 @@ production: 6 yield: 4 potency: 1 - idealLight: 8 chemicals: Vitamin: Min: 1 @@ -273,7 +265,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 8 chemicals: Nutriment: Min: 1 @@ -303,7 +294,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 8 chemicals: Nutriment: Min: 1 @@ -329,7 +319,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 8 chemicals: Haloperidol: Min: 1 @@ -359,7 +348,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 8 growthStages: 3 chemicals: Nutriment: @@ -443,7 +431,6 @@ potency: 20 growthStages: 5 waterConsumption: 0.6 - idealLight: 9 idealHeat: 298 chemicals: Vitamin: @@ -489,7 +476,6 @@ growthStages: 3 waterConsumption: 0.60 nutrientConsumption: 0.50 - lightTolerance: 6 idealHeat: 288 - type: seed @@ -510,7 +496,6 @@ growthStages: 3 waterConsumption: 0.60 nutrientConsumption: 0.80 - lightTolerance: 6 idealHeat: 288 - type: seed @@ -533,9 +518,7 @@ potency: 10 waterConsumption: 0.60 nutrientConsumption: 0.40 - idealLight: 8 idealHeat: 298 - splatPrototype: PuddleSplatter chemicals: Nutriment: Min: 1 @@ -567,9 +550,7 @@ potency: 10 waterConsumption: 0.60 nutrientConsumption: 0.70 - idealLight: 8 idealHeat: 298 - splatPrototype: PuddleSplatter chemicals: Nutriment: Min: 1 @@ -603,9 +584,7 @@ potency: 10 waterConsumption: 0.60 nutrientConsumption: 0.70 - idealLight: 8 idealHeat: 298 - splatPrototype: PuddleSplatter chemicals: Blood: Min: 1 @@ -633,10 +612,8 @@ potency: 10 waterConsumption: 0.60 nutrientConsumption: 0.70 - idealLight: 8 idealHeat: 298 growthStages: 2 - splatPrototype: PuddleSplatter chemicals: Blood: Min: 1 @@ -664,7 +641,6 @@ production: 6 yield: 2 potency: 20 - idealLight: 9 idealHeat: 298 chemicals: Nutriment: @@ -747,7 +723,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 6 chemicals: Nutriment: Min: 1 @@ -773,7 +748,6 @@ production: 6 yield: 3 potency: 10 - idealLight: 6 waterConsumption: 0.75 nutrientConsumption: 0.75 chemicals: @@ -805,7 +779,6 @@ yield: 2 potency: 20 growthStages: 3 - idealLight: 8 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -835,7 +808,6 @@ yield: 2 potency: 20 growthStages: 3 - idealLight: 8 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -867,7 +839,6 @@ yield: 2 potency: 20 growthStages: 3 - idealLight: 8 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -899,7 +870,6 @@ yield: 5 potency: 1 growthStages: 3 - lightTolerance: 6 waterConsumption: 0.60 nutrientConsumption: 0.50 idealHeat: 288 @@ -925,7 +895,6 @@ yield: 2 potency: 20 nutrientConsumption: 0.50 - idealLight: 9 idealHeat: 298 chemicals: Egg: @@ -952,7 +921,6 @@ potency: 20 growthStages: 3 waterConsumption: 0.40 - idealLight: 9 idealHeat: 298 chemicals: THC: @@ -977,7 +945,6 @@ potency: 20 growthStages: 3 waterConsumption: 0.40 - idealLight: 9 idealHeat: 298 chemicals: SpaceDrugs: @@ -1022,7 +989,6 @@ potency: 20 growthStages: 3 waterConsumption: 0.40 - idealLight: 9 idealHeat: 298 chemicals: Nicotine: @@ -1047,7 +1013,6 @@ yield: 2 potency: 20 growthStages: 5 - idealLight: 8 waterConsumption: 0.60 idealHeat: 298 chemicals: @@ -1071,7 +1036,6 @@ yield: 2 potency: 20 growthStages: 5 - idealLight: 8 waterConsumption: 0.70 nutrientConsumption: 0.80 idealHeat: 298 @@ -1102,7 +1066,6 @@ production: 6 yield: 2 potency: 20 - idealLight: 9 idealHeat: 298 chemicals: CapsaicinOil: @@ -1133,7 +1096,6 @@ production: 6 yield: 2 potency: 20 - idealLight: 9 idealHeat: 298 chemicals: Frostoil: @@ -1422,7 +1384,6 @@ yield: 1 potency: 10 growthStages: 2 - idealLight: 6 chemicals: Nutriment: Min: 1 @@ -1448,7 +1409,6 @@ yield: 1 potency: 10 growthStages: 2 - idealLight: 6 chemicals: Nutriment: Min: 1 @@ -1474,7 +1434,6 @@ yield: 1 potency: 10 growthStages: 2 - idealLight: 6 chemicals: Nutriment: Min: 1 @@ -1500,7 +1459,6 @@ yield: 3 potency: 5 growthStages: 4 - idealLight: 5 nutrientConsumption: 0.40 waterConsumption: 0.60 chemicals: @@ -1530,7 +1488,6 @@ production: 6 yield: 3 potency: 5 - idealLight: 7 nutrientConsumption: 0.40 chemicals: Nutriment: @@ -1579,7 +1536,6 @@ production: 6 yield: 3 potency: 5 - idealLight: 7 nutrientConsumption: 0.40 chemicals: Nutriment: @@ -1632,7 +1588,6 @@ production: 3 yield: 1 potency: 1 - idealLight: 8 chemicals: Nutriment: Min: 1 @@ -1661,7 +1616,6 @@ production: 3 yield: 1 potency: 1 - idealLight: 8 chemicals: Nutriment: Min: 1 @@ -1690,7 +1644,6 @@ maturation: 6 production: 6 yield: 6 - idealLight: 7 waterConsumption: 1 nutrientConsumption: 0.8 idealHeat: 298 @@ -1718,7 +1671,6 @@ maturation: 6 production: 6 yield: 4 - idealLight: 7 nutrientConsumption: 0.6 chemicals: Nutriment: @@ -1745,7 +1697,6 @@ production: 6 potency: 10 yield: 3 - idealLight: 8 idealHeat: 298 growthStages: 4 waterConsumption: 0.6 @@ -1776,7 +1727,6 @@ production: 6 yield: 3 potency: 25 - idealLight: 8 harvestRepeat: Repeat nutrientConsumption: 0.5 waterConsumption: 0.5 @@ -1805,7 +1755,6 @@ production: 6 yield: 3 potency: 25 - idealLight: 8 harvestRepeat: Repeat nutrientConsumption: 0.5 waterConsumption: 0.5 @@ -1897,7 +1846,6 @@ production: 3 yield: 3 potency: 5 - idealLight: 8 growthStages: 3 waterConsumption: 0.60 chemicals: @@ -1920,7 +1868,6 @@ production: 3 yield: 2 potency: 5 - idealLight: 8 growthStages: 3 waterConsumption: 0.80 chemicals: @@ -1948,7 +1895,6 @@ production: 6 yield: 5 potency: 10 - idealLight: 6 chemicals: Nutriment: Min: 1