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.Botany.Systems;
|
||||||
using Content.Server.Chemistry.EntitySystems;
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
|
using Content.Server.Ghost.Roles.Components;
|
||||||
using Content.Shared.Botany;
|
using Content.Shared.Botany;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -127,6 +128,13 @@ namespace Content.Server.Botany.Components
|
|||||||
// todo ecs.
|
// todo ecs.
|
||||||
var botanySystem = EntitySystem.Get<BotanySystem>();
|
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.
|
// 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))
|
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;
|
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.
|
// Make sure the plant is not starving.
|
||||||
if (_random.Prob(0.35f))
|
if (_random.Prob(0.35f))
|
||||||
{
|
{
|
||||||
@@ -374,6 +389,13 @@ namespace Content.Server.Botany.Components
|
|||||||
|
|
||||||
CheckLevelSanity();
|
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)
|
if (_updateSpriteAfterUpdate)
|
||||||
UpdateSprite();
|
UpdateSprite();
|
||||||
}
|
}
|
||||||
@@ -535,15 +557,7 @@ namespace Content.Server.Botany.Components
|
|||||||
if (!solutionSystem.TryGetSolution(Owner, SoilSolutionName, out var solution))
|
if (!solutionSystem.TryGetSolution(Owner, SoilSolutionName, out var solution))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (solution.TotalVolume <= 0 || MutationLevel >= 25)
|
if (solution.TotalVolume > 0 && MutationLevel < 25)
|
||||||
{
|
|
||||||
if (MutationLevel >= 0)
|
|
||||||
{
|
|
||||||
Mutate(Math.Min(MutationLevel, 25));
|
|
||||||
MutationLevel = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var amt = FixedPoint2.New(1);
|
var amt = FixedPoint2.New(1);
|
||||||
foreach (var (reagentId, quantity) in solutionSystem.RemoveEachReagent(Owner, solution, amt))
|
foreach (var (reagentId, quantity) in solutionSystem.RemoveEachReagent(Owner, solution, amt))
|
||||||
@@ -558,13 +572,28 @@ namespace Content.Server.Botany.Components
|
|||||||
|
|
||||||
private void Mutate(float severity)
|
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()
|
public void UpdateSprite()
|
||||||
{
|
{
|
||||||
_updateSpriteAfterUpdate = false;
|
_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))
|
if (!_entMan.TryGetComponent<AppearanceComponent>(Owner, out var appearanceComponent))
|
||||||
return;
|
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.
|
// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component.
|
||||||
[Virtual, DataDefinition]
|
[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
|
public class SeedData
|
||||||
{
|
{
|
||||||
#region Tracking
|
#region Tracking
|
||||||
@@ -146,14 +146,14 @@ public class SeedData
|
|||||||
|
|
||||||
[DataField("waterConsumption")] public float WaterConsumption = 3f;
|
[DataField("waterConsumption")] public float WaterConsumption = 3f;
|
||||||
[DataField("idealHeat")] public float IdealHeat = 293f;
|
[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("idealLight")] public float IdealLight = 7f;
|
||||||
[DataField("lightTolerance")] public float LightTolerance = 5f;
|
[DataField("lightTolerance")] public float LightTolerance = 3f;
|
||||||
[DataField("toxinsTolerance")] public float ToxinsTolerance = 4f;
|
[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;
|
[DataField("pestTolerance")] public float PestTolerance = 5f;
|
||||||
|
|
||||||
@@ -174,6 +174,28 @@ public class SeedData
|
|||||||
|
|
||||||
[DataField("potency")] public float Potency = 1f;
|
[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>
|
/// <summary>
|
||||||
/// If true, a sharp tool is required to harvest this plant.
|
/// If true, a sharp tool is required to harvest this plant.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -201,10 +223,12 @@ public class SeedData
|
|||||||
|
|
||||||
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
|
[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;
|
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
|
||||||
|
|
||||||
|
public float BioluminescentRadius = 2f;
|
||||||
|
|
||||||
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -247,14 +271,18 @@ public class SeedData
|
|||||||
HarvestRepeat = HarvestRepeat,
|
HarvestRepeat = HarvestRepeat,
|
||||||
Potency = Potency,
|
Potency = Potency,
|
||||||
|
|
||||||
|
Seedless = Seedless,
|
||||||
|
Viable = Viable,
|
||||||
|
Slip = Slip,
|
||||||
|
Sentient = Sentient,
|
||||||
|
Ligneous = Ligneous,
|
||||||
|
|
||||||
PlantRsi = PlantRsi,
|
PlantRsi = PlantRsi,
|
||||||
PlantIconState = PlantIconState,
|
PlantIconState = PlantIconState,
|
||||||
Bioluminescent = Bioluminescent,
|
Bioluminescent = Bioluminescent,
|
||||||
BioluminescentColor = BioluminescentColor,
|
BioluminescentColor = BioluminescentColor,
|
||||||
SplatPrototype = SplatPrototype,
|
SplatPrototype = SplatPrototype,
|
||||||
|
|
||||||
Ligneous = Ligneous,
|
|
||||||
|
|
||||||
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
||||||
Unique = true,
|
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.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Botany.Components;
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Mind.Commands;
|
||||||
using Content.Server.Kitchen.Components;
|
using Content.Server.Kitchen.Components;
|
||||||
using Content.Shared.Botany;
|
using Content.Shared.Botany;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Random.Helpers;
|
using Content.Shared.Random.Helpers;
|
||||||
|
using Content.Shared.Slippery;
|
||||||
|
using Content.Shared.StepTrigger.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
@@ -158,6 +161,20 @@ public sealed partial class BotanySystem
|
|||||||
metaData.EntityName += "?";
|
metaData.EntityName += "?";
|
||||||
metaData.EntityDescription += " " + Loc.GetString("botany-mysterious-description-addon");
|
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;
|
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;
|
return;
|
||||||
|
|
||||||
if (!TryComp(args.Used, out ProduceComponent? produce)) 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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-interact-message",("name", args.Used)),
|
_popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-interact-message",("name", args.Used)),
|
||||||
Filter.Entities(args.User), PopupType.Medium);
|
Filter.Entities(args.User), PopupType.Medium);
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
|
|||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public sealed class RobustHarvest : ReagentEffect
|
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)
|
public override void Effect(ReagentEffectArgs args)
|
||||||
{
|
{
|
||||||
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|
||||||
@@ -18,10 +27,15 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
|
|||||||
|
|
||||||
var random = IoCManager.Resolve<IRobustRandom>();
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
|
||||||
if (plantHolderComp.Seed.Potency < 100)
|
if (plantHolderComp.Seed.Potency < PotencyLimit)
|
||||||
{
|
{
|
||||||
plantHolderComp.EnsureUniqueSeed();
|
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))
|
else if (plantHolderComp.Seed.Yield > 1 && random.Prob(0.1f))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
## Entity
|
## 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-mask-blocked = {CAPITALIZE(THE($target))} needs to take off {THE($mask)}.
|
||||||
swab-used = It looks like it's been used.
|
swab-used = It looks like it's been used.
|
||||||
swab-unused = It's clean and ready to use.
|
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
|
- state: nitrile
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: mouth swab box
|
name: sterile swab box
|
||||||
parent: BoxCardboard
|
parent: BoxCardboard
|
||||||
id: BoxMouthSwab
|
id: BoxMouthSwab
|
||||||
components:
|
components:
|
||||||
|
|||||||
@@ -99,6 +99,8 @@
|
|||||||
- id: HydroponicsToolClippers
|
- id: HydroponicsToolClippers
|
||||||
- id: ClothingBeltPlant
|
- id: ClothingBeltPlant
|
||||||
- id: PlantBag ##Some maps don't have nutrivend
|
- id: PlantBag ##Some maps don't have nutrivend
|
||||||
|
- id: BoxMouthSwab
|
||||||
|
- id: HandLabeler
|
||||||
- id: TowercapSeeds
|
- id: TowercapSeeds
|
||||||
- id: BananaSeeds
|
- id: BananaSeeds
|
||||||
prob: 0.6
|
prob: 0.6
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
id: DiseaseSwab
|
id: DiseaseSwab
|
||||||
name: mouth swab
|
name: sterile swab
|
||||||
description: Used to take saliva samples to test for diseases.
|
description: Used for taking and transfering samples. Sterile until open. Single use only.
|
||||||
components:
|
components:
|
||||||
- type: Item
|
- type: Item
|
||||||
size: 1
|
size: 1
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
tags:
|
tags:
|
||||||
- Recyclable
|
- Recyclable
|
||||||
- type: DiseaseSwab
|
- type: DiseaseSwab
|
||||||
|
- type: BotanySwab
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
|
|||||||
@@ -140,6 +140,23 @@
|
|||||||
- ReagentId: RobustHarvest
|
- ReagentId: RobustHarvest
|
||||||
Quantity: 30
|
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
|
- type: entity
|
||||||
id: NocturineChemistryBottle
|
id: NocturineChemistryBottle
|
||||||
name: nocturine bottle
|
name: nocturine bottle
|
||||||
|
|||||||
Reference in New Issue
Block a user