diff --git a/Content.Server/Botany/Components/BotanySwabComponent.cs b/Content.Server/Botany/Components/BotanySwabComponent.cs
new file mode 100644
index 0000000000..004bed915d
--- /dev/null
+++ b/Content.Server/Botany/Components/BotanySwabComponent.cs
@@ -0,0 +1,25 @@
+using System.Threading;
+
+namespace Content.Server.Botany
+{
+ ///
+ /// Anything that can be used to cross-pollinate plants.
+ ///
+ [RegisterComponent]
+ public sealed class BotanySwabComponent : Component
+ {
+ [DataField("swabDelay")]
+ [ViewVariables]
+ public float SwabDelay = 2f;
+
+ ///
+ /// Token for interrupting swabbing do after.
+ ///
+ public CancellationTokenSource? CancelToken;
+
+ ///
+ /// SeedData from the first plant that got swabbed.
+ ///
+ public SeedData? SeedData;
+ }
+}
diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs
index c2f651c1e1..6f716e5f1c 100644
--- a/Content.Server/Botany/Components/PlantHolderComponent.cs
+++ b/Content.Server/Botany/Components/PlantHolderComponent.cs
@@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Botany.Systems;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Hands.Components;
+using Content.Server.Ghost.Roles.Components;
using Content.Shared.Botany;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
@@ -127,6 +128,13 @@ namespace Content.Server.Botany.Components
// todo ecs.
var botanySystem = EntitySystem.Get();
+ // Process mutations
+ if (MutationLevel > 0)
+ {
+ Mutate(Math.Min(MutationLevel, 25));
+ MutationLevel = 0;
+ }
+
// Weeds like water and nutrients! They may appear even if there's not a seed planted.
if (WaterLevel > 10 && NutritionLevel > 2 && _random.Prob(Seed == null ? 0.05f : 0.01f))
{
@@ -192,6 +200,13 @@ namespace Content.Server.Botany.Components
var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier;
+ // Make sure genetics are viable.
+ if (!Seed.Viable)
+ {
+ AffectGrowth(-1);
+ Health -= 6*healthMod;
+ }
+
// Make sure the plant is not starving.
if (_random.Prob(0.35f))
{
@@ -374,6 +389,13 @@ namespace Content.Server.Botany.Components
CheckLevelSanity();
+ if (Seed.Sentient)
+ {
+ var comp = _entMan.EnsureComponent(Owner);
+ comp.RoleName = _entMan.GetComponent(Owner).EntityName;
+ comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName));
+ }
+
if (_updateSpriteAfterUpdate)
UpdateSprite();
}
@@ -535,15 +557,7 @@ namespace Content.Server.Botany.Components
if (!solutionSystem.TryGetSolution(Owner, SoilSolutionName, out var solution))
return;
- if (solution.TotalVolume <= 0 || MutationLevel >= 25)
- {
- if (MutationLevel >= 0)
- {
- Mutate(Math.Min(MutationLevel, 25));
- MutationLevel = 0;
- }
- }
- else
+ if (solution.TotalVolume > 0 && MutationLevel < 25)
{
var amt = FixedPoint2.New(1);
foreach (var (reagentId, quantity) in solutionSystem.RemoveEachReagent(Owner, solution, amt))
@@ -558,13 +572,28 @@ namespace Content.Server.Botany.Components
private void Mutate(float severity)
{
- // TODO: Coming soon in "Botany 2: Plant boogaloo".
+ if (Seed != null)
+ {
+ EnsureUniqueSeed();
+ _entMan.System().MutateSeed(Seed, severity);
+ }
}
public void UpdateSprite()
{
_updateSpriteAfterUpdate = false;
+ if (Seed != null && Seed.Bioluminescent)
+ {
+ var light = _entMan.EnsureComponent(Owner);
+ light.Radius = Seed.BioluminescentRadius;
+ light.Color = Seed.BioluminescentColor;
+ }
+ else
+ {
+ _entMan.RemoveComponent(Owner);
+ }
+
if (!_entMan.TryGetComponent(Owner, out var appearanceComponent))
return;
diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs
index a2b09eb149..c01df9aacd 100644
--- a/Content.Server/Botany/SeedPrototype.cs
+++ b/Content.Server/Botany/SeedPrototype.cs
@@ -65,7 +65,7 @@ public 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(ReagentEffect))]
+[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(ReagentEffect), typeof(MutationSystem))]
public class SeedData
{
#region Tracking
@@ -146,14 +146,14 @@ public class SeedData
[DataField("waterConsumption")] public float WaterConsumption = 3f;
[DataField("idealHeat")] public float IdealHeat = 293f;
- [DataField("heatTolerance")] public float HeatTolerance = 20f;
+ [DataField("heatTolerance")] public float HeatTolerance = 10f;
[DataField("idealLight")] public float IdealLight = 7f;
- [DataField("lightTolerance")] public float LightTolerance = 5f;
+ [DataField("lightTolerance")] public float LightTolerance = 3f;
[DataField("toxinsTolerance")] public float ToxinsTolerance = 4f;
- [DataField("lowPressureTolerance")] public float LowPressureTolerance = 25f;
+ [DataField("lowPressureTolerance")] public float LowPressureTolerance = 81f;
- [DataField("highPressureTolerance")] public float HighPressureTolerance = 200f;
+ [DataField("highPressureTolerance")] public float HighPressureTolerance = 121f;
[DataField("pestTolerance")] public float PestTolerance = 5f;
@@ -174,6 +174,28 @@ public class SeedData
[DataField("potency")] public float Potency = 1f;
+ ///
+ /// If true, cannot be harvested for seeds. Balances hybrids and
+ /// mutations.
+ ///
+ [DataField("seedless")] public bool Seedless = false;
+
+ ///
+ /// If true, rapidly decrease health while growing. Used to kill off
+ /// plants with "bad" mutations.
+ ///
+ [DataField("viable")] public bool Viable = true;
+
+ ///
+ /// If true, fruit slips players.
+ ///
+ [DataField("slip")] public bool Slip = false;
+
+ ///
+ /// If true, fruits are sentient.
+ ///
+ [DataField("sentient")] public bool Sentient = false;
+
///
/// If true, a sharp tool is required to harvest this plant.
///
@@ -201,10 +223,12 @@ public class SeedData
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
- [DataField("bioluminescent")] public bool Bioluminescent { get; set; }
+ [DataField("bioluminescent")] public bool Bioluminescent;
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
+ public float BioluminescentRadius = 2f;
+
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
#endregion
@@ -247,14 +271,18 @@ public class SeedData
HarvestRepeat = HarvestRepeat,
Potency = Potency,
+ Seedless = Seedless,
+ Viable = Viable,
+ Slip = Slip,
+ Sentient = Sentient,
+ Ligneous = Ligneous,
+
PlantRsi = PlantRsi,
PlantIconState = PlantIconState,
Bioluminescent = Bioluminescent,
BioluminescentColor = BioluminescentColor,
SplatPrototype = SplatPrototype,
- Ligneous = Ligneous,
-
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
Unique = true,
};
diff --git a/Content.Server/Botany/Systems/BotanySwabSystem.cs b/Content.Server/Botany/Systems/BotanySwabSystem.cs
new file mode 100644
index 0000000000..e372726c66
--- /dev/null
+++ b/Content.Server/Botany/Systems/BotanySwabSystem.cs
@@ -0,0 +1,142 @@
+using System.Threading;
+using Content.Server.Botany.Components;
+using Content.Server.DoAfter;
+using Content.Server.Hands.Components;
+using Content.Server.Nutrition.EntitySystems;
+using Content.Server.Popups;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Inventory;
+using Content.Shared.Tools.Components;
+using Robust.Shared.Audio;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Botany
+{
+ public sealed class BotanySwabSystem : EntitySystem
+ {
+ [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly MutationSystem _mutationSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnAfterInteract);
+ SubscribeLocalEvent(OnExamined);
+ // Private Events
+ SubscribeLocalEvent(OnTargetSwabSuccessful);
+ SubscribeLocalEvent(OnSwabCancelled);
+ }
+
+ ///
+ /// Handles swabbing a plant.
+ ///
+ private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
+ {
+ if (swab.CancelToken != null)
+ {
+ swab.CancelToken.Cancel();
+ swab.CancelToken = null;
+ return;
+ }
+
+ if (args.Target == null || !args.CanReach)
+ return;
+
+ if (!TryComp(args.Target, out var plant))
+ return;
+
+ swab.CancelToken = new CancellationTokenSource();
+ _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
+ {
+ BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, plant),
+ BroadcastCancelledEvent = new SwabCancelledEvent(swab),
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnStun = true,
+ NeedHand = true
+ });
+ }
+
+ ///
+ /// This handles swab examination text
+ /// so you can tell if they are used or not.
+ ///
+ private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange)
+ {
+ if (swab.SeedData != null)
+ args.PushMarkup(Loc.GetString("swab-used"));
+ else
+ args.PushMarkup(Loc.GetString("swab-unused"));
+ }
+ }
+
+ ///
+ /// Save seed data or cross-pollenate.
+ ///
+ private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
+ {
+ if (args.Target == null)
+ return;
+
+ if (args.Swab.SeedData == null)
+ {
+ // Pick up pollen
+ args.Swab.SeedData = args.Plant.Seed;
+ _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Target.Value, Filter.Entities(args.User));
+ }
+ else
+ {
+ var old = args.Plant.Seed; // Save old plant pollen
+ if (old == null)
+ return;
+ args.Plant.Seed = _mutationSystem.Cross(args.Swab.SeedData, old); // Cross-pollenate
+ args.Swab.SeedData = old; // Transfer old plant pollen to swab
+ _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Target.Value, Filter.Entities(args.User));
+ }
+
+ if (args.Swab.CancelToken != null)
+ {
+ args.Swab.CancelToken.Cancel();
+ args.Swab.CancelToken = null;
+ }
+ }
+
+ private static void OnSwabCancelled(SwabCancelledEvent args)
+ {
+ args.Swab.CancelToken = null;
+ }
+
+ private sealed class SwabCancelledEvent : EntityEventArgs
+ {
+ public readonly BotanySwabComponent Swab;
+ public SwabCancelledEvent(BotanySwabComponent swab)
+ {
+ Swab = swab;
+ }
+ }
+
+ private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
+ {
+ public EntityUid User { get; }
+ public EntityUid? Target { get; }
+ public BotanySwabComponent Swab { get; }
+
+ public PlantHolderComponent Plant { get; }
+
+ public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, BotanySwabComponent swab, PlantHolderComponent plant)
+ {
+ User = user;
+ Target = target;
+ Swab = swab;
+ Plant = plant;
+ }
+ }
+ }
+}
+
diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs
index 88e87c4599..7e6d7df2f3 100644
--- a/Content.Server/Botany/Systems/BotanySystem.Seed.cs
+++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs
@@ -1,11 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Botany.Components;
+using Content.Server.Mind.Commands;
using Content.Server.Kitchen.Components;
using Content.Shared.Botany;
using Content.Shared.Examine;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
+using Content.Shared.Slippery;
+using Content.Shared.StepTrigger.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player;
@@ -158,6 +161,20 @@ public sealed partial class BotanySystem
metaData.EntityName += "?";
metaData.EntityDescription += " " + Loc.GetString("botany-mysterious-description-addon");
}
+
+ if (proto.Bioluminescent)
+ {
+ var light = EnsureComp(entity);
+ light.Radius = proto.BioluminescentRadius;
+ light.Color = proto.BioluminescentColor;
+ }
+
+ if (proto.Slip)
+ {
+ var slippery = EnsureComp(entity);
+ EntityManager.Dirty(slippery);
+ EnsureComp(entity);
+ }
}
return products;
diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs
new file mode 100644
index 0000000000..d1cbd58d3d
--- /dev/null
+++ b/Content.Server/Botany/Systems/MutationSystem.cs
@@ -0,0 +1,211 @@
+using Robust.Shared.Random;
+
+namespace Content.Server.Botany;
+
+public class MutationSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+ ///
+ // Main idea: Simulate genetic mutation using random binary flips. Each
+ // seed attribute can be encoded with a variable number of bits, e.g.
+ // NutrientConsumption is represented by 5 bits randomly distributed in the
+ // plant's genome which thermometer code the floating value between 0.1 and
+ // 5. 1 unit of mutation flips one bit in the plant's genome, which changes
+ // NutrientConsumption if one of those 5 bits gets affected.
+ //
+ // You MUST clone() seed before mutating it!
+ ///
+ public void MutateSeed(SeedData seed, float severity)
+ {
+ // Add up everything in the bits column and put the number here.
+ const int totalbits = 215;
+
+ // Tolerances (55)
+ MutateFloat(ref seed.NutrientConsumption , 0.05f , 1.2f , 5 , totalbits , severity);
+ MutateFloat(ref seed.WaterConsumption , 3f , 9f , 5 , totalbits , severity);
+ MutateFloat(ref seed.IdealHeat , 263f , 323f , 5 , totalbits , severity);
+ MutateFloat(ref seed.HeatTolerance , 2f , 25f , 5 , totalbits , severity);
+ MutateFloat(ref seed.IdealLight , 0f , 14f , 5 , totalbits , severity);
+ MutateFloat(ref seed.LightTolerance , 1f , 5f , 5 , totalbits , severity);
+ MutateFloat(ref seed.ToxinsTolerance , 1f , 10f , 5 , totalbits , severity);
+ MutateFloat(ref seed.LowPressureTolerance , 60f , 100f , 5 , totalbits , severity);
+ MutateFloat(ref seed.HighPressureTolerance , 100f , 140f , 5 , totalbits , severity);
+ MutateFloat(ref seed.PestTolerance , 0f , 15f , 5 , totalbits , severity);
+ MutateFloat(ref seed.WeedTolerance , 0f , 15f , 5 , totalbits , severity);
+
+ // Stats (30*2 = 60)
+ MutateFloat(ref seed.Endurance , 50f , 150f , 5 , totalbits , 2*severity);
+ MutateInt(ref seed.Yield , 3 , 10 , 5 , totalbits , 2*severity);
+ MutateFloat(ref seed.Lifespan , 10f , 80f , 5 , totalbits , 2*severity);
+ MutateFloat(ref seed.Maturation , 3f , 8f , 5 , totalbits , 2*severity);
+ MutateFloat(ref seed.Production , 1f , 10f , 5 , totalbits , 2*severity);
+ MutateFloat(ref seed.Potency , 30f , 100f , 5 , totalbits , 2*severity);
+
+ // Kill the plant (30)
+ MutateBool(ref seed.Viable , false , 30 , totalbits , severity);
+
+ // Fun (70)
+ MutateBool(ref seed.Seedless , true , 10 , totalbits , severity);
+ MutateBool(ref seed.Slip , true , 10 , totalbits , severity);
+ MutateBool(ref seed.Sentient , true , 10 , totalbits , severity);
+ MutateBool(ref seed.Ligneous , true , 10 , totalbits , severity);
+ MutateBool(ref seed.Bioluminescent , true , 10 , totalbits , severity);
+ seed.BioluminescentColor = RandomColor(seed.BioluminescentColor, 10, totalbits, severity);
+ }
+
+ public SeedData Cross(SeedData a, SeedData b)
+ {
+ SeedData result = b.Clone();
+
+ result.Chemicals = random(0.5f) ? a.Chemicals : result.Chemicals;
+
+ CrossFloat(ref result.NutrientConsumption, a.NutrientConsumption);
+ CrossFloat(ref result.WaterConsumption, a.WaterConsumption);
+ CrossFloat(ref result.IdealHeat, a.IdealHeat);
+ CrossFloat(ref result.HeatTolerance, a.HeatTolerance);
+ CrossFloat(ref result.IdealLight, a.IdealLight);
+ CrossFloat(ref result.LightTolerance, a.LightTolerance);
+ CrossFloat(ref result.ToxinsTolerance, a.ToxinsTolerance);
+ CrossFloat(ref result.LowPressureTolerance, a.LowPressureTolerance);
+ CrossFloat(ref result.HighPressureTolerance, a.HighPressureTolerance);
+ CrossFloat(ref result.PestTolerance, a.PestTolerance);
+ CrossFloat(ref result.WeedTolerance, a.WeedTolerance);
+
+ CrossFloat(ref result.Endurance, a.Endurance);
+ CrossInt(ref result.Yield, a.Yield);
+ CrossFloat(ref result.Lifespan, a.Lifespan);
+ CrossFloat(ref result.Maturation, a.Maturation);
+ CrossFloat(ref result.Production, a.Production);
+ CrossFloat(ref result.Potency, a.Potency);
+
+ CrossBool(ref result.Seedless, a.Seedless);
+ CrossBool(ref result.Viable, a.Viable);
+ CrossBool(ref result.Slip, a.Slip);
+ CrossBool(ref result.Sentient, a.Sentient);
+ CrossBool(ref result.Ligneous, a.Ligneous);
+ CrossBool(ref result.Bioluminescent, a.Bioluminescent);
+ result.BioluminescentColor = random(0.5f) ? a.BioluminescentColor : result.BioluminescentColor;
+
+ // Hybrids have a high chance of being seedless. Balances very
+ // effective hybrid crossings.
+ if (a.Name == result.Name && random(0.7f))
+ {
+ result.Seedless = true;
+ }
+
+ return result;
+ }
+
+ // Mutate reference 'val' between 'min' and 'max' by pretending the value
+ // is representable by a thermometer code with 'bits' number of bits and
+ // randomly flipping some of them.
+ //
+ // 'totalbits' and 'mult' are used only to calculate the probability that
+ // one bit gets flipped.
+ private void MutateFloat(ref float val, float min, float max, int bits, int totalbits, float mult)
+ {
+ // Probability that a bit flip happens for this value.
+ float p = mult*bits/totalbits;
+ if (!random(p))
+ {
+ return;
+ }
+
+ // Starting number of bits that are high, between 0 and n.
+ int n = (int)Math.Round((val - min) / (max - min) * bits);
+
+ // Probability that the bit flip increases n.
+ float p_increase = 1-(float)n/bits;
+ int np;
+ if (random(p_increase))
+ {
+ np = n + 1;
+ }
+ else
+ {
+ np = n - 1;
+ }
+
+ // Set value based on mutated thermometer code.
+ float nval = MathF.Min(MathF.Max((float)np/bits * (max - min) + min, min), max);
+ val = nval;
+ }
+
+ private void MutateInt(ref int n, int min, int max, int bits, int totalbits, float mult)
+ {
+ // Probability that a bit flip happens for this value.
+ float p = mult*bits/totalbits;
+ if (!random(p))
+ {
+ return;
+ }
+
+ // Probability that the bit flip increases n.
+ float p_increase = 1-(float)n/bits;
+ int np;
+ if (random(p_increase))
+ {
+ np = n + 1;
+ }
+ else
+ {
+ np = n - 1;
+ }
+
+ np = Math.Min(Math.Max(np, min), max);
+ n = np;
+ }
+
+ private void MutateBool(ref bool val, bool polarity, int bits, int totalbits, float mult)
+ {
+ // Probability that a bit flip happens for this value.
+ float p = mult*bits/totalbits;
+ if (!random(p))
+ {
+ return;
+ }
+
+ val = polarity;
+ }
+
+ private Color RandomColor(Color color, int bits, int totalbits, float mult)
+ {
+ float p = mult*bits/totalbits;
+ if (random(p))
+ {
+ var colors = new List{
+ Color.White,
+ Color.Red,
+ Color.Yellow,
+ Color.Green,
+ Color.Blue,
+ Color.Purple,
+ Color.Pink
+ };
+ var rng = IoCManager.Resolve();
+ return rng.Pick(colors);
+ }
+ return color;
+ }
+
+ private void CrossFloat(ref float val, float other)
+ {
+ val = random(0.5f) ? val : other;
+ }
+
+ private void CrossInt(ref int val, int other)
+ {
+ val = random(0.5f) ? val : other;
+ }
+
+ private void CrossBool(ref bool val, bool other)
+ {
+ val = random(0.5f) ? val : other;
+ }
+
+ private bool random(float p)
+ {
+ return _robustRandom.Prob(p);
+ }
+}
diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs
index e8035ceeb2..f2428f42f2 100644
--- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs
+++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs
@@ -28,8 +28,12 @@ public sealed class SeedExtractorSystem : EntitySystem
return;
if (!TryComp(args.Used, out ProduceComponent? produce)) return;
- if (!_botanySystem.TryGetSeed(produce, out var seed))
+ if (!_botanySystem.TryGetSeed(produce, out var seed) || seed.Seedless)
+ {
+ _popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-no-seeds",("name", args.Used)),
+ Filter.Entities(args.User), PopupType.MediumCaution);
return;
+ }
_popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-interact-message",("name", args.Used)),
Filter.Entities(args.User), PopupType.Medium);
diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
index 8a9e31f69b..bc3d985a50 100644
--- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
@@ -9,6 +9,15 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
[DataDefinition]
public sealed class RobustHarvest : ReagentEffect
{
+ [DataField("potencyLimit")]
+ public int PotencyLimit = 50;
+
+ [DataField("potencyIncrease")]
+ public int PotencyIncrease = 3;
+
+ [DataField("potencySeedlessThreshold")]
+ public int PotencySeedlessThreshold = 30;
+
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
@@ -18,10 +27,15 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
var random = IoCManager.Resolve();
- if (plantHolderComp.Seed.Potency < 100)
+ if (plantHolderComp.Seed.Potency < PotencyLimit)
{
plantHolderComp.EnsureUniqueSeed();
- plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + 3, 100);
+ plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + PotencyIncrease, PotencyLimit);
+
+ if (plantHolderComp.Seed.Potency > PotencySeedlessThreshold)
+ {
+ plantHolderComp.Seed.Seedless = true;
+ }
}
else if (plantHolderComp.Seed.Yield > 1 && random.Prob(0.1f))
{
diff --git a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl
index 47296ae1f4..84d5a5ed28 100644
--- a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl
+++ b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl
@@ -1,3 +1,4 @@
## Entity
-seed-extractor-component-interact-message = You extract some seeds from the {$name}.
\ No newline at end of file
+seed-extractor-component-interact-message = You extract some seeds from the { THE($name) }.
+seed-extractor-component-no-seeds = { CAPITALIZE(THE($name)) } has no seeds!
diff --git a/Resources/Locale/en-US/disease/swab.ftl b/Resources/Locale/en-US/disease/swab.ftl
index 92648a21ea..92aaae89e8 100644
--- a/Resources/Locale/en-US/disease/swab.ftl
+++ b/Resources/Locale/en-US/disease/swab.ftl
@@ -3,3 +3,6 @@ swab-swabbed = You swab {THE($target)}'s mouth.
swab-mask-blocked = {CAPITALIZE(THE($target))} needs to take off {THE($mask)}.
swab-used = It looks like it's been used.
swab-unused = It's clean and ready to use.
+
+botany-swab-from = You carefully collect pollen from the plant.
+botany-swab-to = You carefully dust pollen on the plant.
diff --git a/Resources/Prototypes/Catalog/Fills/Boxes/medical.yml b/Resources/Prototypes/Catalog/Fills/Boxes/medical.yml
index 75694428a5..5b8ebe0023 100644
--- a/Resources/Prototypes/Catalog/Fills/Boxes/medical.yml
+++ b/Resources/Prototypes/Catalog/Fills/Boxes/medical.yml
@@ -89,7 +89,7 @@
- state: nitrile
- type: entity
- name: mouth swab box
+ name: sterile swab box
parent: BoxCardboard
id: BoxMouthSwab
components:
diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml
index 730868d673..31f944cb5a 100644
--- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml
+++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml
@@ -99,6 +99,8 @@
- id: HydroponicsToolClippers
- id: ClothingBeltPlant
- id: PlantBag ##Some maps don't have nutrivend
+ - id: BoxMouthSwab
+ - id: HandLabeler
- id: TowercapSeeds
- id: BananaSeeds
prob: 0.6
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/disease.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/disease.yml
index 4626c39e7d..8bc13bd268 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/disease.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/disease.yml
@@ -1,8 +1,8 @@
- type: entity
parent: BaseItem
id: DiseaseSwab
- name: mouth swab
- description: Used to take saliva samples to test for diseases.
+ name: sterile swab
+ description: Used for taking and transfering samples. Sterile until open. Single use only.
components:
- type: Item
size: 1
@@ -14,6 +14,7 @@
tags:
- Recyclable
- type: DiseaseSwab
+ - type: BotanySwab
- type: entity
parent: BaseItem
diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml
index 724db06630..55a10eb3c7 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml
@@ -140,6 +140,23 @@
- ReagentId: RobustHarvest
Quantity: 30
+- type: entity
+ id: UnstableMutagenChemistryBottle
+ name: unstable mutagen bottle
+ description: This will cause rapid mutations in your plants.
+ parent: BaseChemistryEmptyBottle
+ components:
+ - type: SolutionContainerManager
+ solutions:
+ drink:
+ maxVol: 30
+ reagents:
+ - ReagentId: UnstableMutagen
+ Quantity: 30
+ - type: Tag
+ tags:
+ - Bottle
+
- type: entity
id: NocturineChemistryBottle
name: nocturine bottle