Plant genetics (#11407)
This commit is contained in:
25
Content.Server/Botany/Components/BotanySwabComponent.cs
Normal file
25
Content.Server/Botany/Components/BotanySwabComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Content.Server.Botany
|
||||
{
|
||||
/// <summary>
|
||||
/// Anything that can be used to cross-pollinate plants.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class BotanySwabComponent : Component
|
||||
{
|
||||
[DataField("swabDelay")]
|
||||
[ViewVariables]
|
||||
public float SwabDelay = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Token for interrupting swabbing do after.
|
||||
/// </summary>
|
||||
public CancellationTokenSource? CancelToken;
|
||||
|
||||
/// <summary>
|
||||
/// SeedData from the first plant that got swabbed.
|
||||
/// </summary>
|
||||
public SeedData? SeedData;
|
||||
}
|
||||
}
|
||||
@@ -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<BotanySystem>();
|
||||
|
||||
// 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<GhostTakeoverAvailableComponent>(Owner);
|
||||
comp.RoleName = _entMan.GetComponent<MetaDataComponent>(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<MutationSystem>().MutateSeed(Seed, severity);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSprite()
|
||||
{
|
||||
_updateSpriteAfterUpdate = false;
|
||||
|
||||
if (Seed != null && Seed.Bioluminescent)
|
||||
{
|
||||
var light = _entMan.EnsureComponent<PointLightComponent>(Owner);
|
||||
light.Radius = Seed.BioluminescentRadius;
|
||||
light.Color = Seed.BioluminescentColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
_entMan.RemoveComponent<PointLightComponent>(Owner);
|
||||
}
|
||||
|
||||
if (!_entMan.TryGetComponent<AppearanceComponent>(Owner, out var appearanceComponent))
|
||||
return;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// If true, cannot be harvested for seeds. Balances hybrids and
|
||||
/// mutations.
|
||||
/// </summary>
|
||||
[DataField("seedless")] public bool Seedless = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, rapidly decrease health while growing. Used to kill off
|
||||
/// plants with "bad" mutations.
|
||||
/// </summary>
|
||||
[DataField("viable")] public bool Viable = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, fruit slips players.
|
||||
/// </summary>
|
||||
[DataField("slip")] public bool Slip = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, fruits are sentient.
|
||||
/// </summary>
|
||||
[DataField("sentient")] public bool Sentient = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, a sharp tool is required to harvest this plant.
|
||||
/// </summary>
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
142
Content.Server/Botany/Systems/BotanySwabSystem.cs
Normal file
142
Content.Server/Botany/Systems/BotanySwabSystem.cs
Normal file
@@ -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<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
|
||||
// Private Events
|
||||
SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
|
||||
SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles swabbing a plant.
|
||||
/// </summary>
|
||||
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<PlantHolderComponent>(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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This handles swab examination text
|
||||
/// so you can tell if they are used or not.
|
||||
/// </summary>
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save seed data or cross-pollenate.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PointLightComponent>(entity);
|
||||
light.Radius = proto.BioluminescentRadius;
|
||||
light.Color = proto.BioluminescentColor;
|
||||
}
|
||||
|
||||
if (proto.Slip)
|
||||
{
|
||||
var slippery = EnsureComp<SlipperyComponent>(entity);
|
||||
EntityManager.Dirty(slippery);
|
||||
EnsureComp<StepTriggerComponent>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return products;
|
||||
|
||||
211
Content.Server/Botany/Systems/MutationSystem.cs
Normal file
211
Content.Server/Botany/Systems/MutationSystem.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Botany;
|
||||
|
||||
public class MutationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
/// <summary>
|
||||
// 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!
|
||||
/// </summary>
|
||||
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>{
|
||||
Color.White,
|
||||
Color.Red,
|
||||
Color.Yellow,
|
||||
Color.Green,
|
||||
Color.Blue,
|
||||
Color.Purple,
|
||||
Color.Pink
|
||||
};
|
||||
var rng = IoCManager.Resolve<IRobustRandom>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<IRobustRandom>();
|
||||
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
## Entity
|
||||
|
||||
seed-extractor-component-interact-message = You extract some seeds from the {$name}.
|
||||
seed-extractor-component-interact-message = You extract some seeds from the { THE($name) }.
|
||||
seed-extractor-component-no-seeds = { CAPITALIZE(THE($name)) } has no seeds!
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
- state: nitrile
|
||||
|
||||
- type: entity
|
||||
name: mouth swab box
|
||||
name: sterile swab box
|
||||
parent: BoxCardboard
|
||||
id: BoxMouthSwab
|
||||
components:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user