From 6d5c310b36e081c579f775c98f46448cfcf6b1ac Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 5 Sep 2023 21:15:06 +0100 Subject: [PATCH] Make constructed hydro trays start empty (#19620) --- .../Botany/Components/PlantHolderComponent.cs | 125 +- .../Botany/Systems/PlantHolderSystem.cs | 1729 ++++++++--------- .../Circuitboards/Machine/production.yml | 4 +- .../Entities/Structures/hydro_tray.yml | 11 + 4 files changed, 941 insertions(+), 928 deletions(-) diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index 5760cc2d3f..9825167ca2 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -1,90 +1,93 @@ -namespace Content.Server.Botany.Components +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.Botany.Components; + +[RegisterComponent] +public sealed partial class PlantHolderComponent : Component { - [RegisterComponent] - public sealed partial class PlantHolderComponent : Component - { - [ViewVariables] - public TimeSpan NextUpdate = TimeSpan.Zero; - public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); + [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; + [ViewVariables(VVAccess.ReadWrite), DataField("updateDelay")] + public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3); - [ViewVariables] - public int LastProduce; + [DataField("lastProduce")] + public int LastProduce; - [ViewVariables(VVAccess.ReadWrite)] - public int MissingGas; + [ViewVariables(VVAccess.ReadWrite), DataField("missingGas")] + public int MissingGas; - public readonly TimeSpan CycleDelay = TimeSpan.FromSeconds(15f); + [DataField("cycleDelay")] + public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f); - [ViewVariables] - public TimeSpan LastCycle = TimeSpan.Zero; + [DataField("lastCycle", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan LastCycle = TimeSpan.Zero; - [ViewVariables(VVAccess.ReadWrite)] - public bool UpdateSpriteAfterUpdate; + [ViewVariables(VVAccess.ReadWrite), DataField("updateSpriteAfterUpdate")] + public bool UpdateSpriteAfterUpdate; - [ViewVariables(VVAccess.ReadWrite)] [DataField("drawWarnings")] - public bool DrawWarnings = false; + [ViewVariables(VVAccess.ReadWrite), DataField("drawWarnings")] + public bool DrawWarnings = false; - [ViewVariables(VVAccess.ReadWrite)] - public float WaterLevel = 100f; + [ViewVariables(VVAccess.ReadWrite), DataField("waterLevel")] + public float WaterLevel = 100f; - [ViewVariables(VVAccess.ReadWrite)] - public float NutritionLevel = 100f; + [ViewVariables(VVAccess.ReadWrite), DataField("nutritionLevel")] + public float NutritionLevel = 100f; - [ViewVariables(VVAccess.ReadWrite)] - public float PestLevel { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("pestLevel")] + public float PestLevel; - [ViewVariables(VVAccess.ReadWrite)] - public float WeedLevel { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("weedLevel")] + public float WeedLevel; - [ViewVariables(VVAccess.ReadWrite)] - public float Toxins { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("toxins")] + public float Toxins; - [ViewVariables(VVAccess.ReadWrite)] - public int Age { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("age")] + public int Age; - [ViewVariables(VVAccess.ReadWrite)] - public int SkipAging { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("skipAging")] + public int SkipAging; - [ViewVariables(VVAccess.ReadWrite)] - public bool Dead { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("dead")] + public bool Dead; - [ViewVariables(VVAccess.ReadWrite)] - public bool Harvest { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("harvest")] + public bool Harvest; - [ViewVariables(VVAccess.ReadWrite)] - public bool Sampled { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("sampled")] + public bool Sampled; - [ViewVariables(VVAccess.ReadWrite)] - public int YieldMod { get; set; } = 1; + [ViewVariables(VVAccess.ReadWrite), DataField("yieldMod")] + public int YieldMod = 1; - [ViewVariables(VVAccess.ReadWrite)] - public float MutationMod { get; set; } = 1f; + [ViewVariables(VVAccess.ReadWrite), DataField("mutationMod")] + public float MutationMod = 1f; - [ViewVariables(VVAccess.ReadWrite)] - public float MutationLevel { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("mutationLevel")] + public float MutationLevel; - [ViewVariables(VVAccess.ReadWrite)] - public float Health { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("health")] + public float Health; - [ViewVariables(VVAccess.ReadWrite)] - public float WeedCoefficient { get; set; } = 1f; + [ViewVariables(VVAccess.ReadWrite), DataField("weedCoefficient")] + public float WeedCoefficient = 1f; - [ViewVariables(VVAccess.ReadWrite)] - public SeedData? Seed { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("seed")] + public SeedData? Seed; - [ViewVariables(VVAccess.ReadWrite)] - public bool ImproperHeat { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("improperHeat")] + public bool ImproperHeat; - [ViewVariables(VVAccess.ReadWrite)] - public bool ImproperPressure { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("improperPressure")] + public bool ImproperPressure; - [ViewVariables(VVAccess.ReadWrite)] - public bool ImproperLight { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("improperLight")] + public bool ImproperLight; - [ViewVariables(VVAccess.ReadWrite)] - public bool ForceUpdate { get; set; } + [ViewVariables(VVAccess.ReadWrite), DataField("forceUpdate")] + public bool ForceUpdate; - [DataField("solution")] - public string SoilSolutionName { get; set; } = "soil"; - } + [ViewVariables(VVAccess.ReadWrite), DataField("solution")] + public string SoilSolutionName = "soil"; } diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 43255170b2..a59dddb712 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -25,919 +25,918 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; -namespace Content.Server.Botany.Systems +namespace Content.Server.Botany.Systems; + +public sealed class PlantHolderSystem : EntitySystem { - public sealed class PlantHolderSystem : EntitySystem + [Dependency] private readonly BotanySystem _botany = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly MutationSystem _mutation = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + + public const float HydroponicsSpeedMultiplier = 1f; + public const float HydroponicsConsumptionMultiplier = 2f; + + public override void Initialize() { - [Dependency] private readonly BotanySystem _botanySystem = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly MutationSystem _mutation = default!; - [Dependency] private readonly AppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + base.Initialize(); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + } - public const float HydroponicsSpeedMultiplier = 1f; - public const float HydroponicsConsumptionMultiplier = 2f; + public override void Update(float frameTime) + { + base.Update(frameTime); - public override void Initialize() + foreach (var plantHolder in EntityQuery()) { - base.Initialize(); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand); + if (plantHolder.NextUpdate > _gameTiming.CurTime) + continue; + plantHolder.NextUpdate = _gameTiming.CurTime + plantHolder.UpdateDelay; + + Update(plantHolder.Owner, plantHolder); } + } - public override void Update(float frameTime) + private void OnExamine(EntityUid uid, PlantHolderComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + if (component.Seed == null) { - base.Update(frameTime); + args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message")); + } + else if (!component.Dead) + { + var displayName = Loc.GetString(component.Seed.DisplayName); + args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", + ("seedName", displayName), + ("toBeForm", displayName.EndsWith('s') ? "are" : "is"))); - foreach (var plantHolder in EntityQuery()) + if (component.Health <= component.Seed.Endurance / 2) { - if (plantHolder.NextUpdate > _gameTiming.CurTime) - continue; - plantHolder.NextUpdate = _gameTiming.CurTime + plantHolder.UpdateDelay; - - Update(plantHolder.Owner, plantHolder); + args.PushMarkup(Loc.GetString( + "plant-holder-component-something-already-growing-low-health-message", + ("healthState", + Loc.GetString(component.Age > component.Seed.Lifespan + ? "plant-holder-component-plant-old-adjective" + : "plant-holder-component-plant-unhealthy-adjective")))); } } - - private void OnExamine(EntityUid uid, PlantHolderComponent component, ExaminedEvent args) + else { - if (!args.IsInDetailsRange) - return; + args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message")); + } + if (component.WeedLevel >= 5) + args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message")); + + if (component.PestLevel >= 5) + 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))); + args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", + ("nutritionLevel", (int) component.NutritionLevel))); + + if (component.DrawWarnings) + { + if (component.Toxins > 40f) + args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning")); + + if (component.ImproperLight) + args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning")); + + if (component.ImproperHeat) + args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning")); + + if (component.ImproperPressure) + args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning")); + + if (component.MissingGas > 0) + args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning")); + } + } + + private void OnInteractUsing(EntityUid uid, PlantHolderComponent component, InteractUsingEvent args) + { + if (TryComp(args.Used, out SeedComponent? seeds)) + { if (component.Seed == null) { - args.PushMarkup(Loc.GetString("plant-holder-component-nothing-planted-message")); - } - else if (!component.Dead) - { - var displayName = Loc.GetString(component.Seed.DisplayName); - args.PushMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", - ("seedName", displayName), - ("toBeForm", displayName.EndsWith('s') ? "are" : "is"))); - - if (component.Health <= component.Seed.Endurance / 2) - { - args.PushMarkup(Loc.GetString( - "plant-holder-component-something-already-growing-low-health-message", - ("healthState", - Loc.GetString(component.Age > component.Seed.Lifespan - ? "plant-holder-component-plant-old-adjective" - : "plant-holder-component-plant-unhealthy-adjective")))); - } - } - else - { - args.PushMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message")); - } - - if (component.WeedLevel >= 5) - args.PushMarkup(Loc.GetString("plant-holder-component-weed-high-level-message")); - - if (component.PestLevel >= 5) - 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))); - args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", - ("nutritionLevel", (int) component.NutritionLevel))); - - if (component.DrawWarnings) - { - if (component.Toxins > 40f) - args.PushMarkup(Loc.GetString("plant-holder-component-toxins-high-warning")); - - if (component.ImproperLight) - args.PushMarkup(Loc.GetString("plant-holder-component-light-improper-warning")); - - if (component.ImproperHeat) - args.PushMarkup(Loc.GetString("plant-holder-component-heat-improper-warning")); - - if (component.ImproperPressure) - args.PushMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning")); - - if (component.MissingGas > 0) - args.PushMarkup(Loc.GetString("plant-holder-component-gas-missing-warning")); - } - } - - private void OnInteractUsing(EntityUid uid, PlantHolderComponent component, InteractUsingEvent args) - { - if (TryComp(args.Used, out SeedComponent? seeds)) - { - if (component.Seed == null) - { - if (!_botanySystem.TryGetSeed(seeds, out var seed)) - return; - - var name = Loc.GetString(seed.Name); - var noun = Loc.GetString(seed.Noun); - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", - ("seedName", name), - ("seedNoun", noun)), args.User, PopupType.Medium); - - component.Seed = seed; - component.Dead = false; - component.Age = 1; - component.Health = component.Seed.Endurance; - component.LastCycle = _gameTiming.CurTime; - - EntityManager.QueueDeleteEntity(args.Used); - - CheckLevelSanity(uid, component); - UpdateSprite(uid, component); - + if (!_botany.TryGetSeed(seeds, out var seed)) return; - } - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", - ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); - return; - } + var name = Loc.GetString(seed.Name); + var noun = Loc.GetString(seed.Noun); + _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", + ("seedName", name), + ("seedNoun", noun)), args.User, PopupType.Medium); - if (_tagSystem.HasTag(args.Used, "Hoe")) - { - if (component.WeedLevel > 0) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message", - ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); - _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message", - ("otherName", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true); - component.WeedLevel = 0; - UpdateSprite(uid, component); - } - else - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), args.User); - } + component.Seed = seed; + component.Dead = false; + component.Age = 1; + component.Health = component.Seed.Endurance; + component.LastCycle = _gameTiming.CurTime; - return; - } + QueueDel(args.Used); - if (_tagSystem.HasTag(args.Used, "Shovel")) - { - if (component.Seed != null) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message", - ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); - _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message", - ("name", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true); - RemovePlant(uid, component); - } - else - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", - ("name", Comp(uid).EntityName)), args.User); - } - - return; - } - - if (_solutionSystem.TryGetDrainableSolution(args.Used, out var solution) - && _solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var targetSolution) - && TryComp(args.Used, out SprayComponent? spray)) - { - var amount = FixedPoint2.New(1); - - var targetEntity = uid; - var solutionEntity = args.Used; - - _audio.PlayPvs(spray.SpraySound, args.Used, AudioParams.Default.WithVariation(0.125f)); - - var split = _solutionSystem.Drain(solutionEntity, solution, amount); - - if (split.Volume == 0) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", - ("owner", args.Used)), args.User); - return; - } - - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-spray-message", - ("owner", uid), - ("amount", split.Volume)), args.User, PopupType.Medium); - - _solutionSystem.TryAddSolution(targetEntity, targetSolution, split); - - ForceUpdateByExternalCause(uid, component); - - return; - } - - if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) - { - if (component.Seed == null) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User); - return; - } - - if (component.Sampled) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User); - return; - } - - if (component.Dead) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User); - return; - } - - component.Seed.Unique = false; - var seed = _botanySystem.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User); - seed.RandomOffset(0.25f); - var displayName = Loc.GetString(component.Seed.DisplayName); - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", - ("seedName", displayName)), args.User); - component.Health -= (_random.Next(3, 5) * 10); - - if (component.Seed != null && component.Seed.CanScream) - { - _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2)); - } - - if (_random.Prob(0.3f)) - component.Sampled = true; - - // Just in case. CheckLevelSanity(uid, component); - ForceUpdateByExternalCause(uid, component); + UpdateSprite(uid, component); return; } - if (HasComp(args.Used)) - DoHarvest(uid, args.User, component); - - if (TryComp(args.Used, out var produce)) - { - _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-compost-message", - ("owner", uid), - ("usingItem", args.Used)), args.User, PopupType.Medium); - _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message", - ("user", Identity.Entity(args.User, EntityManager)), - ("usingItem", args.Used), - ("owner", uid)), uid, Filter.PvsExcept(args.User), true); - - if (_solutionSystem.TryGetSolution(args.Used, produce.SolutionName, out var solution2)) - { - if (_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution1)) - { - // We try to fit as much of the composted plant's contained solution into the hydroponics tray as we can, - // since the plant will be consumed anyway. - - var fillAmount = FixedPoint2.Min(solution2.Volume, solution1.AvailableVolume); - _solutionSystem.TryAddSolution(uid, solution1, - _solutionSystem.SplitSolution(args.Used, solution2, fillAmount)); - - ForceUpdateByExternalCause(uid, component); - } - } - var seed = produce.Seed; - if (seed != null) - { - var nutrientBonus = seed.Potency / 2.5f; - AdjustNutrient(uid, nutrientBonus, component); - } - EntityManager.QueueDeleteEntity(args.Used); - } + _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", + ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); + return; } - private void OnInteractHand(EntityUid uid, PlantHolderComponent component, InteractHandEvent args) + if (_tagSystem.HasTag(args.Used, "Hoe")) { - DoHarvest(uid, args.User, component); - } - - public void WeedInvasion() - { - // TODO - } - - - public void Update(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - UpdateReagents(uid, component); - - var curTime = _gameTiming.CurTime; - - if (component.ForceUpdate) - component.ForceUpdate = false; - else if (curTime < (component.LastCycle + component.CycleDelay)) - { - if (component.UpdateSpriteAfterUpdate) - UpdateSprite(uid, component); - return; - } - - component.LastCycle = curTime; - - // Process mutations - if (component.MutationLevel > 0) - { - Mutate(uid, Math.Min(component.MutationLevel, 25), component); - component.MutationLevel = 0; - } - - // Weeds like water and nutrients! They may appear even if there's not a seed planted. - if (component.WaterLevel > 10 && component.NutritionLevel > 5) - { - var chance = 0f; - if (component.Seed == null) - chance = 0.05f; - else if (component.Seed.TurnIntoKudzu) - chance = 1f; - else - chance = 0.01f; - - if (_random.Prob(chance)) - component.WeedLevel += 1 + HydroponicsSpeedMultiplier * component.WeedCoefficient; - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - if (component.Seed != null && component.Seed.TurnIntoKudzu - && component.WeedLevel >= component.Seed.WeedHighLevelThreshold) - { - Spawn(component.Seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager)); - component.Seed.TurnIntoKudzu = false; - component.Health = 0; - } - - // There's a chance for a weed explosion to happen if weeds take over. - // Plants that are themselves weeds (WeedTolerance > 8) are unaffected. - if (component.WeedLevel >= 10 && _random.Prob(0.1f)) - { - if (component.Seed == null || component.WeedLevel >= component.Seed.WeedTolerance + 2) - WeedInvasion(); - } - - // If we have no seed planted, or the plant is dead, stop processing here. - if (component.Seed == null || component.Dead) - { - if (component.UpdateSpriteAfterUpdate) - UpdateSprite(uid, component); - - return; - } - - // There's a small chance the pest population increases. - // Can only happen when there's a live seed planted. - if (_random.Prob(0.01f)) - { - component.PestLevel += 0.5f * HydroponicsSpeedMultiplier; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Advance plant age here. - if (component.SkipAging > 0) - component.SkipAging--; - else - { - if (_random.Prob(0.8f)) - component.Age += (int) (1 * HydroponicsSpeedMultiplier); - - component.UpdateSpriteAfterUpdate = true; - } - - // Nutrient consumption. - if (component.Seed.NutrientConsumption > 0 && component.NutritionLevel > 0 && _random.Prob(0.75f)) - { - component.NutritionLevel -= MathF.Max(0f, component.Seed.NutrientConsumption * HydroponicsSpeedMultiplier); - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Water consumption. - if (component.Seed.WaterConsumption > 0 && component.WaterLevel > 0 && _random.Prob(0.75f)) - { - component.WaterLevel -= MathF.Max(0f, - component.Seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier); - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier; - - // Make sure genetics are viable. - if (!component.Seed.Viable) - { - AffectGrowth(uid, -1, component); - component.Health -= 6 * healthMod; - } - - // Make sure the plant is not starving. - if (_random.Prob(0.35f)) - { - if (component.NutritionLevel > 5) - { - component.Health += healthMod; - } - else - { - AffectGrowth(uid, -1, component); - component.Health -= healthMod; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Make sure the plant is not thirsty. - if (_random.Prob(0.35f)) - { - if (component.WaterLevel > 10) - { - component.Health += healthMod; - } - else - { - AffectGrowth(uid, -1, component); - component.Health -= healthMod; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas; - - if (component.Seed.ConsumeGasses.Count > 0) - { - component.MissingGas = 0; - - foreach (var (gas, amount) in component.Seed.ConsumeGasses) - { - if (environment.GetMoles(gas) < amount) - { - component.MissingGas++; - continue; - } - - environment.AdjustMoles(gas, -amount); - } - - if (component.MissingGas > 0) - { - component.Health -= component.MissingGas * HydroponicsSpeedMultiplier; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - } - - // SeedPrototype pressure resistance. - var pressure = environment.Pressure; - if (pressure < component.Seed.LowPressureTolerance || pressure > component.Seed.HighPressureTolerance) - { - component.Health -= healthMod; - component.ImproperPressure = true; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - else - { - component.ImproperPressure = false; - } - - // SeedPrototype ideal temperature. - if (MathF.Abs(environment.Temperature - component.Seed.IdealHeat) > component.Seed.HeatTolerance) - { - component.Health -= healthMod; - component.ImproperHeat = true; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - else - { - component.ImproperHeat = false; - } - - // Gas production. - var exudeCount = component.Seed.ExudeGasses.Count; - if (exudeCount > 0) - { - foreach (var (gas, amount) in component.Seed.ExudeGasses) - { - environment.AdjustMoles(gas, - MathF.Max(1f, MathF.Round(amount * MathF.Round(component.Seed.Potency) / exudeCount))); - } - } - - // Toxin levels beyond the plant's tolerance cause damage. - // They are, however, slowly reduced over time. - if (component.Toxins > 0) - { - var toxinUptake = MathF.Max(1, MathF.Round(component.Toxins / 10f)); - if (component.Toxins > component.Seed.ToxinsTolerance) - { - component.Health -= toxinUptake; - } - - component.Toxins -= toxinUptake; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Weed levels. - if (component.PestLevel > 0) - { - // TODO: Carnivorous plants? - if (component.PestLevel > component.Seed.PestTolerance) - { - component.Health -= HydroponicsSpeedMultiplier; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - // Weed levels. if (component.WeedLevel > 0) { - // TODO: Parasitic plants. - if (component.WeedLevel >= component.Seed.WeedTolerance) - { - component.Health -= HydroponicsSpeedMultiplier; - } - - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - - if (component.Age > component.Seed.Lifespan) - { - component.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier; - if (component.DrawWarnings) - component.UpdateSpriteAfterUpdate = true; - } - else if (component.Age < 0) // Revert back to seed packet! - { - // will put it in the trays hands if it has any, please do not try doing this - _botanySystem.SpawnSeedPacket(component.Seed, Transform(uid).Coordinates, uid); - RemovePlant(uid, component); - component.ForceUpdate = true; - Update(uid, component); - } - - CheckHealth(uid, component); - - if (component.Harvest && component.Seed.HarvestRepeat == HarvestType.SelfHarvest) - AutoHarvest(uid, component); - - // If enough time has passed since the plant was harvested, we're ready to harvest again! - if (!component.Dead && component.Seed.ProductPrototypes.Count > 0) - { - if (component.Age > component.Seed.Production) - { - if (component.Age - component.LastProduce > component.Seed.Production && !component.Harvest) - { - component.Harvest = true; - component.LastProduce = component.Age; - } - } - else - { - if (component.Harvest) - { - component.Harvest = false; - component.LastProduce = component.Age; - } - } - } - - CheckLevelSanity(uid, component); - - if (component.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) + _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message", + ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); + _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message", + ("otherName", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true); + component.WeedLevel = 0; UpdateSprite(uid, component); - } - - //TODO: kill this bullshit - public void CheckLevelSanity(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed != null) - component.Health = MathHelper.Clamp(component.Health, 0, component.Seed.Endurance); + } else { - component.Health = 0f; - component.Dead = false; + _popup.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), args.User); } - component.MutationLevel = MathHelper.Clamp(component.MutationLevel, 0f, 100f); - component.NutritionLevel = MathHelper.Clamp(component.NutritionLevel, 0f, 100f); - component.WaterLevel = MathHelper.Clamp(component.WaterLevel, 0f, 100f); - component.PestLevel = MathHelper.Clamp(component.PestLevel, 0f, 10f); - component.WeedLevel = MathHelper.Clamp(component.WeedLevel, 0f, 10f); - component.Toxins = MathHelper.Clamp(component.Toxins, 0f, 100f); - component.YieldMod = MathHelper.Clamp(component.YieldMod, 0, 2); - component.MutationMod = MathHelper.Clamp(component.MutationMod, 0f, 3f); + return; } - public bool DoHarvest(EntityUid plantholder, EntityUid user, PlantHolderComponent? component = null) + if (_tagSystem.HasTag(args.Used, "Shovel")) { - if (!Resolve(plantholder, ref component)) - return false; - - if (component.Seed == null || Deleted(user)) - return false; - - - if (component.Harvest && !component.Dead) + if (component.Seed != null) { - if (TryComp(user, out var hands)) - { - if (!_botanySystem.CanHarvest(component.Seed, hands.ActiveHandEntity)) - return false; - } - else if (!_botanySystem.CanHarvest(component.Seed)) - { - return false; - } - - _botanySystem.Harvest(component.Seed, user, component.YieldMod); - AfterHarvest(plantholder, component); - return true; + _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message", + ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); + _popup.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message", + ("name", Comp(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true); + RemovePlant(uid, component); + } + else + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", + ("name", Comp(uid).EntityName)), args.User); } - if (!component.Dead) - return false; + return; + } - RemovePlant(plantholder, component); + if (_solutionSystem.TryGetDrainableSolution(args.Used, out var solution) + && _solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var targetSolution) + && TryComp(args.Used, out SprayComponent? spray)) + { + var amount = FixedPoint2.New(1); + + var targetEntity = uid; + var solutionEntity = args.Used; + + _audio.PlayPvs(spray.SpraySound, args.Used, AudioParams.Default.WithVariation(0.125f)); + + var split = _solutionSystem.Drain(solutionEntity, solution, amount); + + if (split.Volume == 0) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", + ("owner", args.Used)), args.User); + return; + } + + _popup.PopupCursor(Loc.GetString("plant-holder-component-spray-message", + ("owner", uid), + ("amount", split.Volume)), args.User, PopupType.Medium); + + _solutionSystem.TryAddSolution(targetEntity, targetSolution, split); + + ForceUpdateByExternalCause(uid, component); + + return; + } + + if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) + { + if (component.Seed == null) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User); + return; + } + + if (component.Sampled) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), args.User); + return; + } + + if (component.Dead) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), args.User); + return; + } + + component.Seed.Unique = false; + var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User); + seed.RandomOffset(0.25f); + var displayName = Loc.GetString(component.Seed.DisplayName); + _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", + ("seedName", displayName)), args.User); + component.Health -= (_random.Next(3, 5) * 10); + + if (component.Seed != null && component.Seed.CanScream) + { + _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2)); + } + + if (_random.Prob(0.3f)) + component.Sampled = true; + + // Just in case. + CheckLevelSanity(uid, component); + ForceUpdateByExternalCause(uid, component); + + return; + } + + if (HasComp(args.Used)) + DoHarvest(uid, args.User, component); + + if (TryComp(args.Used, out var produce)) + { + _popup.PopupCursor(Loc.GetString("plant-holder-component-compost-message", + ("owner", uid), + ("usingItem", args.Used)), args.User, PopupType.Medium); + _popup.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message", + ("user", Identity.Entity(args.User, EntityManager)), + ("usingItem", args.Used), + ("owner", uid)), uid, Filter.PvsExcept(args.User), true); + + if (_solutionSystem.TryGetSolution(args.Used, produce.SolutionName, out var solution2)) + { + if (_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution1)) + { + // We try to fit as much of the composted plant's contained solution into the hydroponics tray as we can, + // since the plant will be consumed anyway. + + var fillAmount = FixedPoint2.Min(solution2.Volume, solution1.AvailableVolume); + _solutionSystem.TryAddSolution(uid, solution1, + _solutionSystem.SplitSolution(args.Used, solution2, fillAmount)); + + ForceUpdateByExternalCause(uid, component); + } + } + var seed = produce.Seed; + if (seed != null) + { + var nutrientBonus = seed.Potency / 2.5f; + AdjustNutrient(uid, nutrientBonus, component); + } + QueueDel(args.Used); + } + } + + private void OnInteractHand(EntityUid uid, PlantHolderComponent component, InteractHandEvent args) + { + DoHarvest(uid, args.User, component); + } + + public void WeedInvasion() + { + // TODO + } + + + public void Update(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + UpdateReagents(uid, component); + + var curTime = _gameTiming.CurTime; + + if (component.ForceUpdate) + component.ForceUpdate = false; + else if (curTime < (component.LastCycle + component.CycleDelay)) + { + if (component.UpdateSpriteAfterUpdate) + UpdateSprite(uid, component); + return; + } + + component.LastCycle = curTime; + + // Process mutations + if (component.MutationLevel > 0) + { + Mutate(uid, Math.Min(component.MutationLevel, 25), component); + component.MutationLevel = 0; + } + + // Weeds like water and nutrients! They may appear even if there's not a seed planted. + if (component.WaterLevel > 10 && component.NutritionLevel > 5) + { + var chance = 0f; + if (component.Seed == null) + chance = 0.05f; + else if (component.Seed.TurnIntoKudzu) + chance = 1f; + else + chance = 0.01f; + + if (_random.Prob(chance)) + component.WeedLevel += 1 + HydroponicsSpeedMultiplier * component.WeedCoefficient; + + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + if (component.Seed != null && component.Seed.TurnIntoKudzu + && component.WeedLevel >= component.Seed.WeedHighLevelThreshold) + { + Spawn(component.Seed.KudzuPrototype, Transform(uid).Coordinates.SnapToGrid(EntityManager)); + component.Seed.TurnIntoKudzu = false; + component.Health = 0; + } + + // There's a chance for a weed explosion to happen if weeds take over. + // Plants that are themselves weeds (WeedTolerance > 8) are unaffected. + if (component.WeedLevel >= 10 && _random.Prob(0.1f)) + { + if (component.Seed == null || component.WeedLevel >= component.Seed.WeedTolerance + 2) + WeedInvasion(); + } + + // If we have no seed planted, or the plant is dead, stop processing here. + if (component.Seed == null || component.Dead) + { + if (component.UpdateSpriteAfterUpdate) + UpdateSprite(uid, component); + + return; + } + + // There's a small chance the pest population increases. + // Can only happen when there's a live seed planted. + if (_random.Prob(0.01f)) + { + component.PestLevel += 0.5f * HydroponicsSpeedMultiplier; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + // Advance plant age here. + if (component.SkipAging > 0) + component.SkipAging--; + else + { + if (_random.Prob(0.8f)) + component.Age += (int) (1 * HydroponicsSpeedMultiplier); + + component.UpdateSpriteAfterUpdate = true; + } + + // Nutrient consumption. + if (component.Seed.NutrientConsumption > 0 && component.NutritionLevel > 0 && _random.Prob(0.75f)) + { + component.NutritionLevel -= MathF.Max(0f, component.Seed.NutrientConsumption * HydroponicsSpeedMultiplier); + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + // Water consumption. + if (component.Seed.WaterConsumption > 0 && component.WaterLevel > 0 && _random.Prob(0.75f)) + { + component.WaterLevel -= MathF.Max(0f, + component.Seed.WaterConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier); + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + var healthMod = _random.Next(1, 3) * HydroponicsSpeedMultiplier; + + // Make sure genetics are viable. + if (!component.Seed.Viable) + { + AffectGrowth(uid, -1, component); + component.Health -= 6 * healthMod; + } + + // Make sure the plant is not starving. + if (_random.Prob(0.35f)) + { + if (component.NutritionLevel > 5) + { + component.Health += healthMod; + } + else + { + AffectGrowth(uid, -1, component); + component.Health -= healthMod; + } + + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + // Make sure the plant is not thirsty. + if (_random.Prob(0.35f)) + { + if (component.WaterLevel > 10) + { + component.Health += healthMod; + } + else + { + AffectGrowth(uid, -1, component); + component.Health -= healthMod; + } + + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + var environment = _atmosphere.GetContainingMixture(uid, true, true) ?? GasMixture.SpaceGas; + + if (component.Seed.ConsumeGasses.Count > 0) + { + component.MissingGas = 0; + + foreach (var (gas, amount) in component.Seed.ConsumeGasses) + { + if (environment.GetMoles(gas) < amount) + { + component.MissingGas++; + continue; + } + + environment.AdjustMoles(gas, -amount); + } + + if (component.MissingGas > 0) + { + component.Health -= component.MissingGas * HydroponicsSpeedMultiplier; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + } + + // SeedPrototype pressure resistance. + var pressure = environment.Pressure; + if (pressure < component.Seed.LowPressureTolerance || pressure > component.Seed.HighPressureTolerance) + { + component.Health -= healthMod; + component.ImproperPressure = true; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + else + { + component.ImproperPressure = false; + } + + // SeedPrototype ideal temperature. + if (MathF.Abs(environment.Temperature - component.Seed.IdealHeat) > component.Seed.HeatTolerance) + { + component.Health -= healthMod; + component.ImproperHeat = true; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + else + { + component.ImproperHeat = false; + } + + // Gas production. + var exudeCount = component.Seed.ExudeGasses.Count; + if (exudeCount > 0) + { + foreach (var (gas, amount) in component.Seed.ExudeGasses) + { + environment.AdjustMoles(gas, + MathF.Max(1f, MathF.Round(amount * MathF.Round(component.Seed.Potency) / exudeCount))); + } + } + + // Toxin levels beyond the plant's tolerance cause damage. + // They are, however, slowly reduced over time. + if (component.Toxins > 0) + { + var toxinUptake = MathF.Max(1, MathF.Round(component.Toxins / 10f)); + if (component.Toxins > component.Seed.ToxinsTolerance) + { + component.Health -= toxinUptake; + } + + component.Toxins -= toxinUptake; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + // Weed levels. + if (component.PestLevel > 0) + { + // TODO: Carnivorous plants? + if (component.PestLevel > component.Seed.PestTolerance) + { + component.Health -= HydroponicsSpeedMultiplier; + } + + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + // Weed levels. + if (component.WeedLevel > 0) + { + // TODO: Parasitic plants. + if (component.WeedLevel >= component.Seed.WeedTolerance) + { + component.Health -= HydroponicsSpeedMultiplier; + } + + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + + if (component.Age > component.Seed.Lifespan) + { + component.Health -= _random.Next(3, 5) * HydroponicsSpeedMultiplier; + if (component.DrawWarnings) + component.UpdateSpriteAfterUpdate = true; + } + else if (component.Age < 0) // Revert back to seed packet! + { + // will put it in the trays hands if it has any, please do not try doing this + _botany.SpawnSeedPacket(component.Seed, Transform(uid).Coordinates, uid); + RemovePlant(uid, component); + component.ForceUpdate = true; + Update(uid, component); + } + + CheckHealth(uid, component); + + if (component.Harvest && component.Seed.HarvestRepeat == HarvestType.SelfHarvest) + AutoHarvest(uid, component); + + // If enough time has passed since the plant was harvested, we're ready to harvest again! + if (!component.Dead && component.Seed.ProductPrototypes.Count > 0) + { + if (component.Age > component.Seed.Production) + { + if (component.Age - component.LastProduce > component.Seed.Production && !component.Harvest) + { + component.Harvest = true; + component.LastProduce = component.Age; + } + } + else + { + if (component.Harvest) + { + component.Harvest = false; + component.LastProduce = component.Age; + } + } + } + + CheckLevelSanity(uid, component); + + if (component.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); + } + + //TODO: kill this bullshit + public void CheckLevelSanity(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Seed != null) + component.Health = MathHelper.Clamp(component.Health, 0, component.Seed.Endurance); + else + { + component.Health = 0f; + component.Dead = false; + } + + component.MutationLevel = MathHelper.Clamp(component.MutationLevel, 0f, 100f); + component.NutritionLevel = MathHelper.Clamp(component.NutritionLevel, 0f, 100f); + component.WaterLevel = MathHelper.Clamp(component.WaterLevel, 0f, 100f); + component.PestLevel = MathHelper.Clamp(component.PestLevel, 0f, 10f); + component.WeedLevel = MathHelper.Clamp(component.WeedLevel, 0f, 10f); + component.Toxins = MathHelper.Clamp(component.Toxins, 0f, 100f); + component.YieldMod = MathHelper.Clamp(component.YieldMod, 0, 2); + component.MutationMod = MathHelper.Clamp(component.MutationMod, 0f, 3f); + } + + public bool DoHarvest(EntityUid plantholder, EntityUid user, PlantHolderComponent? component = null) + { + if (!Resolve(plantholder, ref component)) + return false; + + if (component.Seed == null || Deleted(user)) + return false; + + + if (component.Harvest && !component.Dead) + { + if (TryComp(user, out var hands)) + { + if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity)) + return false; + } + else if (!_botany.CanHarvest(component.Seed)) + { + return false; + } + + _botany.Harvest(component.Seed, user, component.YieldMod); AfterHarvest(plantholder, component); return true; } - public void AutoHarvest(EntityUid uid, PlantHolderComponent? component = null) + if (!component.Dead) + return false; + + RemovePlant(plantholder, component); + AfterHarvest(plantholder, component); + return true; + } + + public void AutoHarvest(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Seed == null || !component.Harvest) + return; + + _botany.AutoHarvest(component.Seed, Transform(uid).Coordinates); + AfterHarvest(uid, component); + } + + private void AfterHarvest(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.Harvest = false; + component.LastProduce = component.Age; + + if (component.Seed != null && component.Seed.CanScream) + _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2)); + + if (component.Seed?.HarvestRepeat == HarvestType.NoRepeat) + RemovePlant(uid, component); + + CheckLevelSanity(uid, component); + UpdateSprite(uid, component); + } + + public void CheckHealth(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Health <= 0) { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed == null || !component.Harvest) - return; - - _botanySystem.AutoHarvest(component.Seed, Transform(uid).Coordinates); - AfterHarvest(uid, component); - } - - private void AfterHarvest(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Harvest = false; - component.LastProduce = component.Age; - - if (component.Seed != null && component.Seed.CanScream) - _audio.PlayPvs(component.Seed.ScreamSound, uid, AudioParams.Default.WithVolume(-2)); - - if (component.Seed?.HarvestRepeat == HarvestType.NoRepeat) - RemovePlant(uid, component); - - CheckLevelSanity(uid, component); - UpdateSprite(uid, component); - } - - public void CheckHealth(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Health <= 0) - { - Die(uid, component); - } - } - - public void Die(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Dead = true; - component.Harvest = false; - component.MutationLevel = 0; - component.YieldMod = 1; - component.MutationMod = 1; - component.ImproperLight = false; - component.ImproperHeat = false; - component.ImproperPressure = false; - component.WeedLevel += 1 * HydroponicsSpeedMultiplier; - component.PestLevel = 0; - UpdateSprite(uid, component); - } - - public void RemovePlant(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.YieldMod = 1; - component.MutationMod = 1; - component.PestLevel = 0; - component.Seed = null; - component.Dead = false; - component.Age = 0; - component.Sampled = false; - component.Harvest = false; - component.ImproperLight = false; - component.ImproperPressure = false; - component.ImproperHeat = false; - - UpdateSprite(uid, component); - } - - public void AffectGrowth(EntityUid uid, int amount, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed == null) - return; - - if (amount > 0) - { - if (component.Age < component.Seed.Maturation) - component.Age += amount; - else if (!component.Harvest && component.Seed.Yield <= 0f) - component.LastProduce -= amount; - } - else - { - if (component.Age < component.Seed.Maturation) - component.SkipAging++; - else if (!component.Harvest && component.Seed.Yield <= 0f) - component.LastProduce += amount; - } - } - - public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.NutritionLevel += amount; - } - - public void AdjustWater(EntityUid uid, float amount, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.WaterLevel += amount; - - // Water dilutes toxins. - if (amount > 0) - { - component.Toxins -= amount * 4f; - } - } - - public void UpdateReagents(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (!_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution)) - return; - - if (solution.Volume > 0 && component.MutationLevel < 25) - { - var amt = FixedPoint2.New(1); - foreach (var entry in _solutionSystem.RemoveEachReagent(uid, solution, amt)) - { - var reagentProto = _prototype.Index(entry.Reagent.Prototype); - reagentProto.ReactionPlant(uid, entry, solution); - } - } - - CheckLevelSanity(uid, component); - } - - private void Mutate(EntityUid uid, float severity, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed != null) - { - EnsureUniqueSeed(uid, component); - _mutation.MutateSeed(component.Seed, severity); - } - } - - public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.UpdateSpriteAfterUpdate = false; - - if (component.Seed != null && component.Seed.Bioluminescent) - { - var light = EnsureComp(uid); - light.Radius = component.Seed.BioluminescentRadius; - light.Color = component.Seed.BioluminescentColor; - light.CastShadows = false; // this is expensive, and botanists make lots of plants - Dirty(light); - } - else - { - RemComp(uid); - } - - if (!TryComp(uid, out var app)) - return; - - if (component.Seed != null) - { - if (component.DrawWarnings) - { - _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= component.Seed.Endurance / 2f); - } - - if (component.Dead) - { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "dead", app); - } - else if (component.Harvest) - { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "harvest", app); - } - else if (component.Age < component.Seed.Maturation) - { - var growthStage = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation)); - - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{growthStage}", app); - component.LastProduce = component.Age; - } - else - { - _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); - _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{component.Seed.GrowthStages}", app); - } - } - else - { - _appearance.SetData(uid, PlantHolderVisuals.PlantState, "", app); - _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app); - } - - if (!component.DrawWarnings) - return; - - _appearance.SetData(uid, PlantHolderVisuals.WaterLight, component.WaterLevel <= 15, app); - _appearance.SetData(uid, PlantHolderVisuals.NutritionLight, component.NutritionLevel <= 8, app); - _appearance.SetData(uid, PlantHolderVisuals.AlertLight, - component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat || - component.ImproperLight || component.ImproperPressure || component.MissingGas > 0, app); - _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, component.Harvest, app); - } - - /// - /// Check if the currently contained seed is unique. If it is not, clone it so that we have a unique seed. - /// Necessary to avoid modifying global seeds. - /// - public void EnsureUniqueSeed(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component.Seed is { Unique: false }) - component.Seed = component.Seed.Clone(); - } - - public void ForceUpdateByExternalCause(EntityUid uid, PlantHolderComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.SkipAging++; // We're forcing an update cycle, so one age hasn't passed. - component.ForceUpdate = true; - Update(uid, component); + Die(uid, component); } } + + public void Die(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.Dead = true; + component.Harvest = false; + component.MutationLevel = 0; + component.YieldMod = 1; + component.MutationMod = 1; + component.ImproperLight = false; + component.ImproperHeat = false; + component.ImproperPressure = false; + component.WeedLevel += 1 * HydroponicsSpeedMultiplier; + component.PestLevel = 0; + UpdateSprite(uid, component); + } + + public void RemovePlant(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.YieldMod = 1; + component.MutationMod = 1; + component.PestLevel = 0; + component.Seed = null; + component.Dead = false; + component.Age = 0; + component.Sampled = false; + component.Harvest = false; + component.ImproperLight = false; + component.ImproperPressure = false; + component.ImproperHeat = false; + + UpdateSprite(uid, component); + } + + public void AffectGrowth(EntityUid uid, int amount, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Seed == null) + return; + + if (amount > 0) + { + if (component.Age < component.Seed.Maturation) + component.Age += amount; + else if (!component.Harvest && component.Seed.Yield <= 0f) + component.LastProduce -= amount; + } + else + { + if (component.Age < component.Seed.Maturation) + component.SkipAging++; + else if (!component.Harvest && component.Seed.Yield <= 0f) + component.LastProduce += amount; + } + } + + public void AdjustNutrient(EntityUid uid, float amount, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.NutritionLevel += amount; + } + + public void AdjustWater(EntityUid uid, float amount, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.WaterLevel += amount; + + // Water dilutes toxins. + if (amount > 0) + { + component.Toxins -= amount * 4f; + } + } + + public void UpdateReagents(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (!_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution)) + return; + + if (solution.Volume > 0 && component.MutationLevel < 25) + { + var amt = FixedPoint2.New(1); + foreach (var entry in _solutionSystem.RemoveEachReagent(uid, solution, amt)) + { + var reagentProto = _prototype.Index(entry.Reagent.Prototype); + reagentProto.ReactionPlant(uid, entry, solution); + } + } + + CheckLevelSanity(uid, component); + } + + private void Mutate(EntityUid uid, float severity, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Seed != null) + { + EnsureUniqueSeed(uid, component); + _mutation.MutateSeed(component.Seed, severity); + } + } + + public void UpdateSprite(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.UpdateSpriteAfterUpdate = false; + + if (component.Seed != null && component.Seed.Bioluminescent) + { + var light = EnsureComp(uid); + light.Radius = component.Seed.BioluminescentRadius; + light.Color = component.Seed.BioluminescentColor; + light.CastShadows = false; // this is expensive, and botanists make lots of plants + Dirty(uid, light); + } + else + { + RemComp(uid); + } + + if (!TryComp(uid, out var app)) + return; + + if (component.Seed != null) + { + if (component.DrawWarnings) + { + _appearance.SetData(uid, PlantHolderVisuals.HealthLight, component.Health <= component.Seed.Endurance / 2f); + } + + if (component.Dead) + { + _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantHolderVisuals.PlantState, "dead", app); + } + else if (component.Harvest) + { + _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantHolderVisuals.PlantState, "harvest", app); + } + else if (component.Age < component.Seed.Maturation) + { + var growthStage = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation)); + + _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{growthStage}", app); + component.LastProduce = component.Age; + } + else + { + _appearance.SetData(uid, PlantHolderVisuals.PlantRsi, component.Seed.PlantRsi.ToString(), app); + _appearance.SetData(uid, PlantHolderVisuals.PlantState, $"stage-{component.Seed.GrowthStages}", app); + } + } + else + { + _appearance.SetData(uid, PlantHolderVisuals.PlantState, "", app); + _appearance.SetData(uid, PlantHolderVisuals.HealthLight, false, app); + } + + if (!component.DrawWarnings) + return; + + _appearance.SetData(uid, PlantHolderVisuals.WaterLight, component.WaterLevel <= 15, app); + _appearance.SetData(uid, PlantHolderVisuals.NutritionLight, component.NutritionLevel <= 8, app); + _appearance.SetData(uid, PlantHolderVisuals.AlertLight, + component.WeedLevel >= 5 || component.PestLevel >= 5 || component.Toxins >= 40 || component.ImproperHeat || + component.ImproperLight || component.ImproperPressure || component.MissingGas > 0, app); + _appearance.SetData(uid, PlantHolderVisuals.HarvestLight, component.Harvest, app); + } + + /// + /// Check if the currently contained seed is unique. If it is not, clone it so that we have a unique seed. + /// Necessary to avoid modifying global seeds. + /// + public void EnsureUniqueSeed(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Seed is { Unique: false }) + component.Seed = component.Seed.Clone(); + } + + public void ForceUpdateByExternalCause(EntityUid uid, PlantHolderComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.SkipAging++; // We're forcing an update cycle, so one age hasn't passed. + component.ForceUpdate = true; + Update(uid, component); + } } diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 5f4b742a12..9ecd8c36f1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -435,7 +435,7 @@ - type: Sprite state: service - type: MachineBoard - prototype: hydroponicsTray + prototype: HydroponicsTrayEmpty materialRequirements: # replacing the console screen Glass: 5 @@ -975,4 +975,4 @@ materialRequirements: Steel: 5 CableHV: 5 - Uranium: 2 \ No newline at end of file + Uranium: 2 diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 6c9e305029..944ff6ebff 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -89,3 +89,14 @@ - type: GuideHelp guides: - Botany + +- type: entity + parent: hydroponicsTray + id: HydroponicsTrayEmpty + suffix: Empty + components: + - type: PlantHolder + waterLevel: 0 + nutritionLevel: 0 + # for the lights to update immediately + updateSpriteAfterUpdate: true