diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index 809af737ac..8218bead72 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -6,90 +6,90 @@ namespace Content.Server.Botany.Components; [RegisterComponent] public sealed partial class PlantHolderComponent : Component { - [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan NextUpdate = TimeSpan.Zero; - [ViewVariables(VVAccess.ReadWrite), DataField("updateDelay")] + [DataField] public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); - [DataField("lastProduce")] + [DataField] public int LastProduce; - [ViewVariables(VVAccess.ReadWrite), DataField("missingGas")] + [DataField] public int MissingGas; - [DataField("cycleDelay")] + [DataField] public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f); - [DataField("lastCycle", customTypeSerializer: typeof(TimeOffsetSerializer))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan LastCycle = TimeSpan.Zero; - [ViewVariables(VVAccess.ReadWrite), DataField("updateSpriteAfterUpdate")] + [DataField] public bool UpdateSpriteAfterUpdate; - [ViewVariables(VVAccess.ReadWrite), DataField("drawWarnings")] + [DataField] public bool DrawWarnings = false; - [ViewVariables(VVAccess.ReadWrite), DataField("waterLevel")] + [DataField] public float WaterLevel = 100f; - [ViewVariables(VVAccess.ReadWrite), DataField("nutritionLevel")] + [DataField] public float NutritionLevel = 100f; - [ViewVariables(VVAccess.ReadWrite), DataField("pestLevel")] + [DataField] public float PestLevel; - [ViewVariables(VVAccess.ReadWrite), DataField("weedLevel")] + [DataField] public float WeedLevel; - [ViewVariables(VVAccess.ReadWrite), DataField("toxins")] + [DataField] public float Toxins; - [ViewVariables(VVAccess.ReadWrite), DataField("age")] + [DataField] public int Age; - [ViewVariables(VVAccess.ReadWrite), DataField("skipAging")] + [DataField] public int SkipAging; - [ViewVariables(VVAccess.ReadWrite), DataField("dead")] + [DataField] public bool Dead; - [ViewVariables(VVAccess.ReadWrite), DataField("harvest")] + [DataField] public bool Harvest; - [ViewVariables(VVAccess.ReadWrite), DataField("sampled")] + [DataField] public bool Sampled; - [ViewVariables(VVAccess.ReadWrite), DataField("yieldMod")] + [DataField] public int YieldMod = 1; - [ViewVariables(VVAccess.ReadWrite), DataField("mutationMod")] + [DataField] public float MutationMod = 1f; - [ViewVariables(VVAccess.ReadWrite), DataField("mutationLevel")] + [DataField] public float MutationLevel; - [ViewVariables(VVAccess.ReadWrite), DataField("health")] + [DataField] public float Health; - [ViewVariables(VVAccess.ReadWrite), DataField("weedCoefficient")] + [DataField] public float WeedCoefficient = 1f; - [ViewVariables(VVAccess.ReadWrite), DataField("seed")] + [DataField] public SeedData? Seed; - [ViewVariables(VVAccess.ReadWrite), DataField("improperHeat")] + [DataField] public bool ImproperHeat; - [ViewVariables(VVAccess.ReadWrite), DataField("improperPressure")] + [DataField] public bool ImproperPressure; - [ViewVariables(VVAccess.ReadWrite), DataField("improperLight")] + [DataField] public bool ImproperLight; - [ViewVariables(VVAccess.ReadWrite), DataField("forceUpdate")] + [DataField] public bool ForceUpdate; - [ViewVariables(VVAccess.ReadWrite), DataField("solution")] + [DataField] public string SoilSolutionName = "soil"; [DataField] diff --git a/Content.Server/Botany/Components/ProduceComponent.cs b/Content.Server/Botany/Components/ProduceComponent.cs index b3c4e1c95a..db4ed62dd3 100644 --- a/Content.Server/Botany/Components/ProduceComponent.cs +++ b/Content.Server/Botany/Components/ProduceComponent.cs @@ -13,12 +13,12 @@ public sealed partial class ProduceComponent : SharedProduceComponent /// /// Seed data used to create a when this produce has its seeds extracted. /// - [DataField("seed")] + [DataField] public SeedData? Seed; /// /// Seed data used to create a when this produce has its seeds extracted. /// - [DataField("seedId", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string? SeedId; } diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index 39f06a6436..7a3e08883d 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -2,6 +2,7 @@ using Content.Server.Botany.Components; using Content.Server.Botany.Systems; using Content.Shared.Atmos; using Content.Shared.EntityEffects; +using Content.Shared.Random; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -132,78 +133,67 @@ public partial class SeedData [DataField("productPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List ProductPrototypes = new(); - [DataField("chemicals")] public Dictionary Chemicals = new(); + [DataField] public Dictionary Chemicals = new(); - [DataField("consumeGasses")] public Dictionary ConsumeGasses = new(); + [DataField] public Dictionary ConsumeGasses = new(); - [DataField("exudeGasses")] public Dictionary ExudeGasses = new(); + [DataField] public Dictionary ExudeGasses = new(); #endregion #region Tolerances - [DataField("nutrientConsumption")] public float NutrientConsumption = 0.75f; + [DataField] public float NutrientConsumption = 0.75f; - [DataField("waterConsumption")] public float WaterConsumption = 0.5f; - [DataField("idealHeat")] public float IdealHeat = 293f; - [DataField("heatTolerance")] public float HeatTolerance = 10f; - [DataField("idealLight")] public float IdealLight = 7f; - [DataField("lightTolerance")] public float LightTolerance = 3f; - [DataField("toxinsTolerance")] public float ToxinsTolerance = 4f; + [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("lowPressureTolerance")] public float LowPressureTolerance = 81f; + [DataField] public float LowPressureTolerance = 81f; - [DataField("highPressureTolerance")] public float HighPressureTolerance = 121f; + [DataField] public float HighPressureTolerance = 121f; - [DataField("pestTolerance")] public float PestTolerance = 5f; + [DataField] public float PestTolerance = 5f; - [DataField("weedTolerance")] public float WeedTolerance = 5f; + [DataField] public float WeedTolerance = 5f; - [DataField("weedHighLevelThreshold")] public float WeedHighLevelThreshold = 10f; + [DataField] public float WeedHighLevelThreshold = 10f; #endregion #region General traits - [DataField("endurance")] public float Endurance = 100f; + [DataField] public float Endurance = 100f; - [DataField("yield")] public int Yield; - [DataField("lifespan")] public float Lifespan; - [DataField("maturation")] public float Maturation; - [DataField("production")] public float Production; - [DataField("growthStages")] public int GrowthStages = 6; + [DataField] public int Yield; + [DataField] public float Lifespan; + [DataField] public float Maturation; + [DataField] public float Production; + [DataField] public int GrowthStages = 6; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("harvestRepeat")] public HarvestType HarvestRepeat = HarvestType.NoRepeat; + [DataField] public HarvestType HarvestRepeat = HarvestType.NoRepeat; - [DataField("potency")] public float Potency = 1f; + [DataField] public float Potency = 1f; /// /// If true, cannot be harvested for seeds. Balances hybrids and /// mutations. /// - [DataField("seedless")] public bool Seedless = false; + [DataField] public bool Seedless = false; /// /// If false, 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; + [DataField] public bool Viable = true; /// /// If true, a sharp tool is required to harvest this plant. /// - [DataField("ligneous")] public bool Ligneous; + [DataField] public bool Ligneous; // No, I'm not removing these. // if you re-add these, make sure that they get cloned. @@ -222,36 +212,35 @@ public partial class SeedData #region Cosmetics - [DataField("plantRsi", required: true)] + [DataField(required: true)] public ResPath PlantRsi { get; set; } = default!; - [DataField("plantIconState")] public string PlantIconState { get; set; } = "produce"; + [DataField] public string PlantIconState { get; set; } = "produce"; /// - /// Screams random sound, could be strict sound SoundPathSpecifier or collection SoundCollectionSpecifier - /// base class is SoundSpecifier + /// Screams random sound from collection SoundCollectionSpecifier /// - [DataField("screamSound")] + [DataField] public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("PlantScreams", AudioParams.Default.WithVolume(-10)); [DataField("screaming")] public bool CanScream; - [DataField("bioluminescent")] public bool Bioluminescent; - [DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White; + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string KudzuPrototype = "WeakKudzu"; - public float BioluminescentRadius = 2f; - - [DataField("kudzuPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string KudzuPrototype = "WeakKudzu"; - - [DataField("turnIntoKudzu")] public bool TurnIntoKudzu; - [DataField("splatPrototype")] public string? SplatPrototype { get; set; } + [DataField] public bool TurnIntoKudzu; + [DataField] public string? SplatPrototype { get; set; } #endregion + /// + /// The mutation effects that have been applied to this plant. + /// + [DataField] public List Mutations { get; set; } = new(); + /// /// The seed prototypes this seed may mutate into when prompted to. /// - [DataField("mutationPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] public List MutationPrototypes = new(); public SeedData Clone() @@ -295,17 +284,14 @@ public partial class SeedData Seedless = Seedless, Viable = Viable, - Slip = Slip, - Sentient = Sentient, Ligneous = Ligneous, PlantRsi = PlantRsi, PlantIconState = PlantIconState, - Bioluminescent = Bioluminescent, CanScream = CanScream, TurnIntoKudzu = TurnIntoKudzu, - BioluminescentColor = BioluminescentColor, SplatPrototype = SplatPrototype, + Mutations = Mutations, // Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified. Unique = true, @@ -356,18 +342,16 @@ public partial class SeedData HarvestRepeat = HarvestRepeat, Potency = Potency, + Mutations = Mutations, + Seedless = Seedless, Viable = Viable, - Slip = Slip, - Sentient = Sentient, Ligneous = Ligneous, PlantRsi = other.PlantRsi, PlantIconState = other.PlantIconState, - Bioluminescent = Bioluminescent, CanScream = CanScream, TurnIntoKudzu = TurnIntoKudzu, - BioluminescentColor = BioluminescentColor, SplatPrototype = other.SplatPrototype, // Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified. diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs index 34559a8304..8fdf96f57b 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Produce.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs @@ -1,4 +1,5 @@ using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; using Content.Shared.FixedPoint; namespace Content.Server.Botany.Systems; @@ -10,6 +11,15 @@ public sealed partial class BotanySystem if (!TryGetSeed(produce, out var seed)) return; + foreach (var mutation in seed.Mutations) + { + if (mutation.AppliesToProduce) + { + var args = new EntityEffectBaseArgs(uid, EntityManager); + mutation.Effect.Effect(args); + } + } + if (!_solutionContainerSystem.EnsureSolution(uid, produce.SolutionName, out var solutionContainer, diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs index c988e5338c..1487ed71d4 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Seed.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs @@ -5,16 +5,11 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Botany; using Content.Shared.Examine; using Content.Shared.Hands.EntitySystems; -using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Random; 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.Physics; -using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -34,7 +29,6 @@ public sealed partial class BotanySystem : EntitySystem [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly FixtureSystem _fixtureSystem = default!; - [Dependency] private readonly CollisionWakeSystem _colWakeSystem = default!; [Dependency] private readonly RandomHelperSystem _randomHelper = default!; public override void Initialize() @@ -183,30 +177,6 @@ public sealed partial class BotanySystem : EntitySystem _metaData.SetEntityDescription(entity, metaData.EntityDescription + " " + Loc.GetString("botany-mysterious-description-addon"), metaData); } - - if (proto.Bioluminescent) - { - var light = _light.EnsureLight(entity); - _light.SetRadius(entity, proto.BioluminescentRadius, light); - _light.SetColor(entity, proto.BioluminescentColor, light); - // TODO: Ayo why you copy-pasting code between here and plantholder? - _light.SetCastShadows(entity, false, light); // this is expensive, and botanists make lots of plants - } - - if (proto.Slip) - { - var slippery = EnsureComp(entity); - Dirty(entity, slippery); - EnsureComp(entity); - // Need a fixture with a slip layer in order to actually do the slipping - var fixtures = EnsureComp(entity); - var body = EnsureComp(entity); - var shape = fixtures.Fixtures["fix1"].Shape; - _fixtureSystem.TryCreateFixture(entity, shape, "slips", 1, false, (int) CollisionGroup.SlipLayer, manager: fixtures, body: body); - // Need to disable collision wake so that mobs can collide with and slip on it - var collisionWake = EnsureComp(entity); - _colWakeSystem.SetEnabled(entity, false, collisionWake); - } } return products; diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs index d3159655f5..07a24d19f6 100644 --- a/Content.Server/Botany/Systems/MutationSystem.cs +++ b/Content.Server/Botany/Systems/MutationSystem.cs @@ -1,9 +1,9 @@ +using Content.Shared.Atmos; +using Content.Shared.EntityEffects; +using Content.Shared.Random; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; using System.Linq; -using Content.Shared.Atmos; namespace Content.Server.Botany; @@ -11,25 +11,40 @@ public sealed class MutationSystem : EntitySystem { [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private WeightedRandomFillSolutionPrototype _randomChems = default!; - + private RandomPlantMutationListPrototype _randomMutations = default!; public override void Initialize() { - _randomChems = _prototypeManager.Index("RandomPickBotanyReagent"); + _randomMutations = _prototypeManager.Index("RandomPlantMutations"); } /// - /// 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! + /// For each random mutation, see if it occurs on this plant this check. /// - public void MutateSeed(ref SeedData seed, float severity) + /// + /// + public void CheckRandomMutations(EntityUid plantHolder, ref SeedData seed, float severity) + { + foreach (var mutation in _randomMutations.mutations) + { + if (Random(mutation.BaseOdds * severity)) + { + if (mutation.AppliesToPlant) + { + var args = new EntityEffectBaseArgs(plantHolder, EntityManager); + mutation.Effect.Effect(args); + } + // Stat adjustments do not persist by being an attached effect, they just change the stat. + if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name)) + seed.Mutations.Add(mutation); + } + } + } + + /// + /// 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) { @@ -37,57 +52,7 @@ public sealed class MutationSystem : EntitySystem return; } - // Add up everything in the bits column and put the number here. - const int totalbits = 262; - - #pragma warning disable IDE0055 // disable formatting warnings because this looks more readable - // 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 (72) - MutateBool(ref seed.Seedless , true , 10, totalbits, severity); - MutateBool(ref seed.Slip , true , 10, totalbits, severity); - MutateBool(ref seed.Sentient , true , 2 , totalbits, severity); - MutateBool(ref seed.Ligneous , true , 10, totalbits, severity); - MutateBool(ref seed.Bioluminescent, true , 10, totalbits, severity); - MutateBool(ref seed.TurnIntoKudzu , true , 10, totalbits, severity); - MutateBool(ref seed.CanScream , true , 10, totalbits, severity); - seed.BioluminescentColor = RandomColor(seed.BioluminescentColor, 10, totalbits, severity); - #pragma warning restore IDE0055 - - // ConstantUpgade (10) - MutateHarvestType(ref seed.HarvestRepeat, 10, totalbits, severity); - - // Gas (5) - MutateGasses(ref seed.ExudeGasses, 0.01f, 0.5f, 4, totalbits, severity); - MutateGasses(ref seed.ConsumeGasses, 0.01f, 0.5f, 1, totalbits, severity); - - // Chems (20) - MutateChemicals(ref seed.Chemicals, 20, totalbits, severity); - - // Species (10) - MutateSpecies(ref seed, 10, totalbits, severity); + CheckRandomMutations(plantHolder, ref seed, severity); } public SeedData Cross(SeedData a, SeedData b) @@ -115,19 +80,18 @@ public sealed class MutationSystem : EntitySystem CrossFloat(ref result.Production, a.Production); CrossFloat(ref result.Potency, a.Potency); - // we do not transfer Sentient to another plant to avoid ghost role spam CrossBool(ref result.Seedless, a.Seedless); - CrossBool(ref result.Viable, a.Viable); - CrossBool(ref result.Slip, a.Slip); CrossBool(ref result.Ligneous, a.Ligneous); - CrossBool(ref result.Bioluminescent, a.Bioluminescent); CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu); CrossBool(ref result.CanScream, a.CanScream); CrossGasses(ref result.ExudeGasses, a.ExudeGasses); CrossGasses(ref result.ConsumeGasses, a.ConsumeGasses); - result.BioluminescentColor = Random(0.5f) ? a.BioluminescentColor : result.BioluminescentColor; + // LINQ Explanation + // For the list of mutation effects on both plants, use a 50% chance to pick each one. + // Union all of the chosen mutations into one list, and pick ones with a Distinct (unique) name. + result.Mutations = result.Mutations.Where(m => Random(0.5f)).Union(a.Mutations.Where(m => Random(0.5f))).DistinctBy(m => m.Name).ToList(); // Hybrids have a high chance of being seedless. Balances very // effective hybrid crossings. @@ -139,206 +103,6 @@ public sealed class MutationSystem : EntitySystem 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's representation in thermometer code. - float probBitflip = mult * bits / totalbits; - probBitflip = Math.Clamp(probBitflip, 0, 1); - if (!Random(probBitflip)) - return; - - if (min == max) - { - val = min; - return; - } - - // Starting number of bits that are high, between 0 and bits. - // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. - int valInt = (int)MathF.Round((val - min) / (max - min) * bits); - // val may be outside the range of min/max due to starting prototype values, so clamp. - valInt = Math.Clamp(valInt, 0, bits); - - // Probability that the bit flip increases n. - // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it. - // In other words, it tends to go to the middle. - float probIncrease = 1 - (float)valInt / bits; - int valIntMutated; - if (Random(probIncrease)) - { - valIntMutated = valInt + 1; - } - else - { - valIntMutated = valInt - 1; - } - - // Set value based on mutated thermometer code. - float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max); - val = valMutated; - } - - private void MutateInt(ref int val, int min, int max, int bits, int totalbits, float mult) - { - // Probability that a bit flip happens for this value's representation in thermometer code. - float probBitflip = mult * bits / totalbits; - probBitflip = Math.Clamp(probBitflip, 0, 1); - if (!Random(probBitflip)) - return; - - if (min == max) - { - val = min; - return; - } - - // Starting number of bits that are high, between 0 and bits. - // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. - int valInt = (int)MathF.Round((val - min) / (max - min) * bits); - // val may be outside the range of min/max due to starting prototype values, so clamp. - valInt = Math.Clamp(valInt, 0, bits); - - // Probability that the bit flip increases n. - // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it. - // In other words, it tends to go to the middle. - float probIncrease = 1 - (float)valInt / bits; - int valMutated; - if (Random(probIncrease)) - { - valMutated = val + 1; - } - else - { - valMutated = val - 1; - } - - valMutated = Math.Clamp(valMutated, min, max); - val = valMutated; - } - - private void MutateBool(ref bool val, bool polarity, int bits, int totalbits, float mult) - { - // Probability that a bit flip happens for this value. - float probSet = mult * bits / totalbits; - probSet = Math.Clamp(probSet, 0, 1); - if (!Random(probSet)) - return; - - val = polarity; - } - - private void MutateHarvestType(ref HarvestType val, int bits, int totalbits, float mult) - { - float probModify = mult * bits / totalbits; - probModify = Math.Clamp(probModify, 0, 1); - - if (!Random(probModify)) - return; - - if (val == HarvestType.NoRepeat) - val = HarvestType.Repeat; - else if (val == HarvestType.Repeat) - val = HarvestType.SelfHarvest; - } - - private void MutateGasses(ref Dictionary gasses, float min, float max, int bits, int totalbits, float mult) - { - float probModify = mult * bits / totalbits; - probModify = Math.Clamp(probModify, 0, 1); - if (!Random(probModify)) - return; - - // Add a random amount of a random gas to this gas dictionary - float amount = _robustRandom.NextFloat(min, max); - Gas gas = _robustRandom.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); - if (gasses.ContainsKey(gas)) - { - gasses[gas] += amount; - } - else - { - gasses.Add(gas, amount); - } - } - - private void MutateChemicals(ref Dictionary chemicals, int bits, int totalbits, float mult) - { - float probModify = mult * bits / totalbits; - probModify = Math.Clamp(probModify, 0, 1); - if (!Random(probModify)) - return; - - // Add a random amount of a random chemical to this set of chemicals - if (_randomChems != null) - { - var pick = _randomChems.Pick(_robustRandom); - string chemicalId = pick.reagent; - int amount = _robustRandom.Next(1, (int)pick.quantity); - SeedChemQuantity seedChemQuantity = new SeedChemQuantity(); - if (chemicals.ContainsKey(chemicalId)) - { - seedChemQuantity.Min = chemicals[chemicalId].Min; - seedChemQuantity.Max = chemicals[chemicalId].Max + amount; - } - else - { - seedChemQuantity.Min = 1; - seedChemQuantity.Max = 1 + amount; - seedChemQuantity.Inherent = false; - } - int potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max); - seedChemQuantity.PotencyDivisor = potencyDivisor; - chemicals[chemicalId] = seedChemQuantity; - } - } - - private void MutateSpecies(ref SeedData seed, int bits, int totalbits, float mult) - { - float p = mult * bits / totalbits; - p = Math.Clamp(p, 0, 1); - if (!Random(p)) - return; - - if (seed.MutationPrototypes.Count == 0) - return; - - var targetProto = _robustRandom.Pick(seed.MutationPrototypes); - _prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed); - - if (protoSeed == null) - { - Log.Error($"Seed prototype could not be found: {targetProto}!"); - return; - } - - seed = seed.SpeciesChange(protoSeed); - } - - private Color RandomColor(Color color, int bits, int totalbits, float mult) - { - float probModify = mult * bits / totalbits; - if (Random(probModify)) - { - var colors = new List{ - Color.White, - Color.Red, - Color.Yellow, - Color.Green, - Color.Blue, - Color.Purple, - Color.Pink - }; - return _robustRandom.Pick(colors); - } - return color; - } - private void CrossChemicals(ref Dictionary val, Dictionary other) { // Go through chemicals from the pollen in swab diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 002a054339..0fdca029b7 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -1,8 +1,6 @@ -using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; using Content.Server.Fluids.Components; -using Content.Server.Ghost.Roles.Components; using Content.Server.Kitchen.Components; using Content.Server.Popups; using Content.Shared.Chemistry.EntitySystems; @@ -79,7 +77,7 @@ public sealed class PlantHolderSystem : EntitySystem if (component.Seed == null) return 0; - var result = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation)); + var result = Math.Max(1, (int)(component.Age * component.Seed.GrowthStages / component.Seed.Maturation)); return result; } @@ -125,9 +123,9 @@ public sealed class PlantHolderSystem : EntitySystem args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message")); args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message", - ("waterLevel", (int) component.WaterLevel))); + ("waterLevel", (int)component.WaterLevel))); args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", - ("nutritionLevel", (int) component.NutritionLevel))); + ("nutritionLevel", (int)component.NutritionLevel))); if (component.DrawWarnings) { @@ -299,21 +297,12 @@ public sealed class PlantHolderSystem : EntitySystem healthOverride = component.Health; } var packetSeed = component.Seed; - if (packetSeed.Sentient) - { - packetSeed = packetSeed.Clone(); // clone before modifying the seed - packetSeed.Sentient = false; - } - else - { - packetSeed.Unique = false; - } 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)) @@ -459,7 +448,7 @@ public sealed class PlantHolderSystem : EntitySystem else { if (_random.Prob(0.8f)) - component.Age += (int) (1 * HydroponicsSpeedMultiplier); + component.Age += (int)(1 * HydroponicsSpeedMultiplier); component.UpdateSpriteAfterUpdate = true; } @@ -632,12 +621,6 @@ public sealed class PlantHolderSystem : EntitySystem else if (component.Age < 0) // Revert back to seed packet! { var packetSeed = component.Seed; - if (packetSeed.Sentient) - { - if (!packetSeed.Unique) // clone if necessary before modifying the seed - packetSeed = packetSeed.Clone(); - packetSeed.Sentient = false; // remove Sentient to avoid ghost role spam - } // 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); @@ -674,14 +657,6 @@ public sealed class PlantHolderSystem : EntitySystem CheckLevelSanity(uid, component); - if (component.Seed.Sentient) - { - var ghostRole = EnsureComp(uid); - EnsureComp(uid); - ghostRole.RoleName = MetaData(uid).EntityName; - ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName)); - } - if (component.UpdateSpriteAfterUpdate) UpdateSprite(uid, component); } @@ -911,7 +886,7 @@ public sealed class PlantHolderSystem : EntitySystem if (component.Seed != null) { EnsureUniqueSeed(uid, component); - _mutation.MutateSeed(ref component.Seed, severity); + _mutation.MutateSeed(uid, ref component.Seed, severity); } } @@ -922,19 +897,6 @@ public sealed class PlantHolderSystem : EntitySystem component.UpdateSpriteAfterUpdate = false; - if (component.Seed != null && component.Seed.Bioluminescent) - { - var light = EnsureComp(uid); - _pointLight.SetRadius(uid, component.Seed.BioluminescentRadius, light); - _pointLight.SetColor(uid, component.Seed.BioluminescentColor, light); - _pointLight.SetCastShadows(uid, false, light); - Dirty(uid, light); - } - else - { - RemComp(uid); - } - if (!TryComp(uid, out var app)) return; diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index 4a0d56bfe9..93f76473ff 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -43,12 +43,6 @@ public sealed class SeedExtractorSystem : EntitySystem var coords = Transform(uid).Coordinates; var packetSeed = seed; - if (packetSeed.Sentient) - { - if (!packetSeed.Unique) // clone if necessary before modifying the seed - packetSeed = packetSeed.Clone(); - packetSeed.Sentient = false; // remove Sentient to avoid ghost role spam - } if (amount > 1) packetSeed.Unique = false; diff --git a/Content.Server/EntityEffects/Effects/Glow.cs b/Content.Server/EntityEffects/Effects/Glow.cs new file mode 100644 index 0000000000..9f03476729 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Glow.cs @@ -0,0 +1,48 @@ +using Content.Shared.EntityEffects; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Makes a mob glow. +/// +public sealed partial class Glow : EntityEffect +{ + [DataField] + public float Radius = 2f; + + [DataField] + public Color Color = Color.Black; + + private static readonly List Colors = new() + { + Color.White, + Color.Red, + Color.Yellow, + Color.Green, + Color.Blue, + Color.Purple, + Color.Pink + }; + + public override void Effect(EntityEffectBaseArgs args) + { + if (Color == Color.Black) + { + var random = IoCManager.Resolve(); + Color = random.Pick(Colors); + } + + var lightSystem = args.EntityManager.System(); + var light = lightSystem.EnsureLight(args.TargetEntity); + lightSystem.SetRadius(args.TargetEntity, Radius, light); + lightSystem.SetColor(args.TargetEntity, Color, light); + lightSystem.SetCastShadows(args.TargetEntity, false, light); // this is expensive, and botanists make lots of plants + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} diff --git a/Content.Server/EntityEffects/Effects/PlantChangeStat.cs b/Content.Server/EntityEffects/Effects/PlantChangeStat.cs new file mode 100644 index 0000000000..9592ff779d --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PlantChangeStat.cs @@ -0,0 +1,142 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.PlantMetabolism; + +[UsedImplicitly] +public sealed partial class PlantChangeStat : EntityEffect +{ + [DataField] + public string TargetValue; + + [DataField] + public float MinValue; + + [DataField] + public float MaxValue; + + [DataField] + public int Steps; + + public override void Effect(EntityEffectBaseArgs args) + { + var plantHolder = args.EntityManager.GetComponent(args.TargetEntity); + if (plantHolder == null || plantHolder.Seed == null) + return; + + var member = plantHolder.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?"); + return; + } + + var currentValObj = member.GetValue(plantHolder.Seed); + if (currentValObj == null) + return; + + if (member.FieldType == typeof(float)) + { + var floatVal = (float)currentValObj; + MutateFloat(ref floatVal, MinValue, MaxValue, Steps); + member.SetValue(plantHolder.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); + } + else if (member.FieldType == typeof(bool)) + { + var boolVal = (bool)currentValObj; + boolVal = !boolVal; + member.SetValue(plantHolder.Seed, boolVal); + } + } + + // 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. + private void MutateFloat(ref float val, float min, float max, int bits) + { + if (min == max) + { + val = min; + return; + } + + // Starting number of bits that are high, between 0 and bits. + // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. + int valInt = (int)MathF.Round((val - min) / (max - min) * bits); + // val may be outside the range of min/max due to starting prototype values, so clamp. + valInt = Math.Clamp(valInt, 0, bits); + + // Probability that the bit flip increases n. + // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it. + // In other words, it tends to go to the middle. + float probIncrease = 1 - (float)valInt / bits; + int valIntMutated; + if (Random(probIncrease)) + { + valIntMutated = valInt + 1; + } + else + { + valIntMutated = valInt - 1; + } + + // Set value based on mutated thermometer code. + float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max); + val = valMutated; + } + + private void MutateInt(ref int val, int min, int max, int bits) + { + if (min == max) + { + val = min; + return; + } + + // Starting number of bits that are high, between 0 and bits. + // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. + int valInt = (int)MathF.Round((val - min) / (max - min) * bits); + // val may be outside the range of min/max due to starting prototype values, so clamp. + valInt = Math.Clamp(valInt, 0, bits); + + // Probability that the bit flip increases n. + // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it. + // In other words, it tends to go to the middle. + float probIncrease = 1 - (float)valInt / bits; + int valMutated; + if (Random(probIncrease)) + { + valMutated = val + 1; + } + else + { + valMutated = val - 1; + } + + valMutated = Math.Clamp(valMutated, min, max); + val = valMutated; + } + + private bool Random(float odds) + { + var random = IoCManager.Resolve(); + return random.Prob(odds); + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + throw new NotImplementedException(); + } +} diff --git a/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs b/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs new file mode 100644 index 0000000000..7ee6cd13d7 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PlantMutateChemicals.cs @@ -0,0 +1,55 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.Random; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// changes the chemicals available in a plant's produce +/// +public sealed partial class PlantMutateChemicals : EntityEffect +{ + public override void Effect(EntityEffectBaseArgs args) + { + var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + + if (plantholder.Seed == null) + return; + + var random = IoCManager.Resolve(); + var prototypeManager = IoCManager.Resolve(); + var chemicals = plantholder.Seed.Chemicals; + var randomChems = prototypeManager.Index("RandomPickBotanyReagent").Fills; + + // Add a random amount of a random chemical to this set of chemicals + if (randomChems != null) + { + var pick = random.Pick(randomChems); + var chemicalId = random.Pick(pick.Reagents); + var amount = random.Next(1, (int)pick.Quantity); + var seedChemQuantity = new SeedChemQuantity(); + if (chemicals.ContainsKey(chemicalId)) + { + seedChemQuantity.Min = chemicals[chemicalId].Min; + seedChemQuantity.Max = chemicals[chemicalId].Max + amount; + } + else + { + seedChemQuantity.Min = 1; + seedChemQuantity.Max = 1 + amount; + seedChemQuantity.Inherent = false; + } + var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max); + seedChemQuantity.PotencyDivisor = potencyDivisor; + chemicals[chemicalId] = seedChemQuantity; + } + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} diff --git a/Content.Server/EntityEffects/Effects/PlantMutateGases.cs b/Content.Server/EntityEffects/Effects/PlantMutateGases.cs new file mode 100644 index 0000000000..52b9da3a85 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PlantMutateGases.cs @@ -0,0 +1,87 @@ +using Content.Server.Botany.Components; +using Content.Shared.Atmos; +using Content.Shared.EntityEffects; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using System.Linq; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// changes the gases that a plant or produce create. +/// +public sealed partial class PlantMutateExudeGasses : EntityEffect +{ + [DataField] + public float MinValue = 0.01f; + + [DataField] + public float MaxValue = 0.5f; + + public override void Effect(EntityEffectBaseArgs args) + { + var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + + if (plantholder.Seed == null) + return; + + var random = IoCManager.Resolve(); + var gasses = plantholder.Seed.ExudeGasses; + + // Add a random amount of a random gas to this gas dictionary + float amount = random.NextFloat(MinValue, MaxValue); + Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); + if (gasses.ContainsKey(gas)) + { + gasses[gas] += amount; + } + else + { + gasses.Add(gas, amount); + } + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} + +/// +/// changes the gases that a plant or produce consumes. +/// +public sealed partial class PlantMutateConsumeGasses : EntityEffect +{ + [DataField] + public float MinValue = 0.01f; + + [DataField] + public float MaxValue = 0.5f; + public override void Effect(EntityEffectBaseArgs args) + { + var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + + if (plantholder.Seed == null) + return; + + var random = IoCManager.Resolve(); + var gasses = plantholder.Seed.ConsumeGasses; + + // Add a random amount of a random gas to this gas dictionary + float amount = random.NextFloat(MinValue, MaxValue); + Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); + if (gasses.ContainsKey(gas)) + { + gasses[gas] += amount; + } + else + { + gasses.Add(gas, amount); + } + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} diff --git a/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs b/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs new file mode 100644 index 0000000000..e67176ee16 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PlantMutateHarvest.cs @@ -0,0 +1,30 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Robust.Shared.Prototypes; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Upgrades a plant's harvest type. +/// +public sealed partial class PlantMutateHarvest : EntityEffect +{ + public override void Effect(EntityEffectBaseArgs args) + { + var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + + if (plantholder.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; + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} diff --git a/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs b/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs new file mode 100644 index 0000000000..65bd59daa3 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PlantSpeciesChange.cs @@ -0,0 +1,43 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Serilog; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Changes a plant into one of the species its able to mutate into. +/// +public sealed partial class PlantSpeciesChange : EntityEffect +{ + public override void Effect(EntityEffectBaseArgs args) + { + var prototypeManager = IoCManager.Resolve(); + var plantholder = args.EntityManager.GetComponent(args.TargetEntity); + + if (plantholder.Seed == null) + return; + + if (plantholder.Seed.MutationPrototypes.Count == 0) + return; + + var random = IoCManager.Resolve(); + var targetProto = random.Pick(plantholder.Seed.MutationPrototypes); + prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed); + + if (protoSeed == null) + { + Log.Error($"Seed prototype could not be found: {targetProto}!"); + return; + } + + plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed); + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return "TODO"; + } +} diff --git a/Content.Server/EntityEffects/Effects/Slipify.cs b/Content.Server/EntityEffects/Effects/Slipify.cs new file mode 100644 index 0000000000..bc1cc062a3 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Slipify.cs @@ -0,0 +1,38 @@ +using Content.Shared.EntityEffects; +using Content.Shared.Physics; +using Content.Shared.Slippery; +using Content.Shared.StepTrigger.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Makes a mob slippery. +/// +public sealed partial class Slipify : EntityEffect +{ + public override void Effect(EntityEffectBaseArgs args) + { + var fixtureSystem = args.EntityManager.System(); + var colWakeSystem = args.EntityManager.System(); + var slippery = args.EntityManager.EnsureComponent(args.TargetEntity); + args.EntityManager.Dirty(args.TargetEntity, slippery); + args.EntityManager.EnsureComponent(args.TargetEntity); + // Need a fixture with a slip layer in order to actually do the slipping + var fixtures = args.EntityManager.EnsureComponent(args.TargetEntity); + var body = args.EntityManager.EnsureComponent(args.TargetEntity); + var shape = fixtures.Fixtures["fix1"].Shape; + fixtureSystem.TryCreateFixture(args.TargetEntity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: fixtures, body: body); + // Need to disable collision wake so that mobs can collide with and slip on it + var collisionWake = args.EntityManager.EnsureComponent(args.TargetEntity); + colWakeSystem.SetEnabled(args.TargetEntity, false, collisionWake); + } + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + throw new NotImplementedException(); + } +} diff --git a/Content.Shared/Random/RandomPlantMutation.cs b/Content.Shared/Random/RandomPlantMutation.cs new file mode 100644 index 0000000000..d95cf7bf42 --- /dev/null +++ b/Content.Shared/Random/RandomPlantMutation.cs @@ -0,0 +1,48 @@ +using Content.Shared.EntityEffects; +using Robust.Shared.Serialization; + +namespace Content.Shared.Random; + +/// +/// Data that specifies the odds and effects of possible random plant mutations. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class RandomPlantMutation +{ + /// + /// Odds of this mutation occurring with 1 point of mutation severity on a plant. + /// + [DataField] + public float BaseOdds = 0; + + /// + /// The name of this mutation. + /// + [DataField] + public string Name = ""; + + /// + /// The actual EntityEffect to apply to the target + /// + [DataField] + public EntityEffect Effect = default!; + + /// + /// This mutation will target the harvested produce + /// + [DataField] + public bool AppliesToProduce = true; + + /// + /// This mutation will target the growing plant as soon as this mutation is applied. + /// + [DataField] + public bool AppliesToPlant = true; + + /// + /// This mutation stays on the plant and its produce. If false while AppliesToPlant is true, the effect will run when triggered. + /// + [DataField] + public bool Persists = true; +} diff --git a/Content.Shared/Random/RandomPlantMutationListPrototype.cs b/Content.Shared/Random/RandomPlantMutationListPrototype.cs new file mode 100644 index 0000000000..84e3b9256c --- /dev/null +++ b/Content.Shared/Random/RandomPlantMutationListPrototype.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Random; + +/// +/// Random weighting dataset for solutions, able to specify reagents quantity. +/// +[Prototype("RandomPlantMutationList")] +public sealed partial class RandomPlantMutationListPrototype : IPrototype +{ + [IdDataField] public string ID { get; } = default!; + + /// + /// List of RandomFills that can be picked from. + /// + [DataField("mutations", required: true, serverOnly: true)] + public List mutations = new(); +} diff --git a/Resources/Prototypes/Hydroponics/mutations.yml b/Resources/Prototypes/Hydroponics/randomChemicals.yml similarity index 100% rename from Resources/Prototypes/Hydroponics/mutations.yml rename to Resources/Prototypes/Hydroponics/randomChemicals.yml diff --git a/Resources/Prototypes/Hydroponics/randomMutations.yml b/Resources/Prototypes/Hydroponics/randomMutations.yml new file mode 100644 index 0000000000..50f6845ec3 --- /dev/null +++ b/Resources/Prototypes/Hydroponics/randomMutations.yml @@ -0,0 +1,178 @@ +- type: RandomPlantMutationList + id: RandomPlantMutations + mutations: + - name: Bioluminescent + baseOdds: 0.036 + effect: !type:Glow + - name: Sentient + baseOdds: 0.0072 + appliesToPlant: false # makes the botany tray sentient if true + effect: !type:MakeSentient # existing effect. + - name: Slippery + baseOdds: 0.036 + effect: !type:Slipify + - name: ChangeSpecies + baseOdds: 0.036 + appliesToProduce: false + effect: !type:PlantSpeciesChange + - name: Unviable + baseOdds: 0.109 + persists: false + effect: !type:PlantChangeStat + targetValue: Viable + - name: ChangeWaterConsumption + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: WaterConsumption + minValue: 0.3 + maxValue: 0.9 + steps: 5 + - name: ChangeNutrientConsumption + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: NutrientConsumption + minValue: 0.05 + maxValue: 1.2 + steps: 5 + - name: ChangeIdealHeat + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: IdealHeat + minValue: 263 + maxValue: 323 + steps: 5 + - name: ChangeHeatTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: HeatTolerance + minValue: 2 + maxValue: 25 + steps: 5 + - name: ChangeToxinsTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: ToxinsTolerance + minValue: 1 + maxValue: 10 + steps: 5 + - name: ChangeLowPressureTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: LowPressureTolerance + minValue: 60 + maxValue: 100 + steps: 5 + - name: ChangeHighPressureTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: HighPressureTolerance + minValue: 100 + maxValue: 140 + steps: 5 + - name: ChangePestTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: PestTolerance + minValue: 0 + maxValue: 15 + steps: 5 + - name: ChangeWeedTolerance + baseOdds: 0.018 + persists: false + effect: !type:PlantChangeStat + targetValue: WeedTolerance + minValue: 0 + maxValue: 15 + steps: 5 + - name: ChangeEndurance + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Endurance + minValue: 50 + maxValue: 150 + steps: 5 + - name: ChangeYield + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Yield + minValue: 3 + maxValue: 10 + steps: 5 + - name: ChangeLifespan + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Lifespan + minValue: 10 + maxValue: 80 + steps: 5 + - name: ChangeMaturation + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Maturation + minValue: 3 + maxValue: 8 + steps: 5 + - name: ChangeProduction + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Production + minValue: 1 + maxValue: 10 + steps: 5 + - name: ChangePotency + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Potency + minValue: 30 + maxValue: 100 + steps: 5 + - name: ChangeSeedless + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Seedless + - name: ChangeLigneous + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: Ligneous + - name: ChangeTurnIntoKudzu + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: TurnIntoKudzu + - name: ChangeScreaming + baseOdds: 0.036 + persists: false + effect: !type:PlantChangeStat + targetValue: CanScream + - name: ChangeChemicals + baseOdds: 0.072 + persists: false + effect: !type:PlantMutateChemicals + - name: ChangeExudeGasses + baseOdds: 0.0145 + persists: false + effect: !type:PlantMutateExudeGasses + - name: ChangeConsumeGasses + baseOdds: 0.0036 + persists: false + effect: !type:PlantMutateConsumeGasses + - name: ChangeHarvest + baseOdds: 0.036 + persists: false + effect: !type:PlantMutateHarvest \ No newline at end of file