mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 13:37:47 +03:00
<!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description Adds the East-Orion Company as a selectable Employer. The reason this company is being added is simple; there are a number of employers spread out over the various jobs on the station. However, there is no company yet that gave Hydroponics any attention. With my Nederlandic nationality already hinting at farming and agriculture, I decided that this might be something I could expand upon to give Hydroponics some representation among megacorporations. The East-Orion Company takes inspiration from various real-life sources. The most obvious arethese: New-Nederic, referencing New-Holland, a tractor manufacturer, representing the Hydroponics side of the East-Orion Company Farmer's Civil Union, referencing the BoerenBurgerBond, a political party representing farmers across the Netherlands, who I used as inspiration for the Security-side of the East-Orion Company. The Hydroponics uniform uses colours taken from New-Holland tractors, while the FCU security jumpsuits mimic the design of the police uniform used in the Netherlands. In addition, the East-Orion Company Botanists can select a new plant clipper in their loadout. This functions the same as normal plant clippers, except that they deal less damage to plants when taking a sample from them. --- <details><summary><h1>Media</h1></summary> <p>      </p> </details> --- # Changelog 🆑 - add: Added the East-Orion Company as a selectable Employer, focusing on Hydroponics. They have alternate Hydroponics uniforms, alternate Security uniforms, and a high-quality plant clipper that is more gentle on plants when taking a sample. --------- Signed-off-by: Timfa <timfalken@hotmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> (cherry picked from commit 24bb2ac123b74a9622c5bb5ff6eb2413423839d9)
989 lines
36 KiB
C#
989 lines
36 KiB
C#
using Content.Server.Atmos;
|
|
using Content.Server.Atmos.EntitySystems;
|
|
using Content.Server.Botany.Components;
|
|
using Content.Server.Chemistry.Containers.EntitySystems;
|
|
using Content.Server.Fluids.Components;
|
|
using Content.Server.Ghost.Roles.Components;
|
|
using Content.Server.Kitchen.Components;
|
|
using Content.Server.Popups;
|
|
using Content.Shared.Atmos;
|
|
using Content.Shared.Botany;
|
|
using Content.Shared.Burial.Components;
|
|
using Content.Shared.Chemistry.Reagent;
|
|
using Content.Shared.Coordinates.Helpers;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Random;
|
|
using Content.Shared.Tag;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Server.Botany.Systems;
|
|
|
|
public sealed class PlantHolderSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
|
[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 SharedPointLightSystem _pointLight = default!;
|
|
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
|
[Dependency] private readonly TagSystem _tagSystem = default!;
|
|
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
|
|
|
|
public const float HydroponicsSpeedMultiplier = 1f;
|
|
public const float HydroponicsConsumptionMultiplier = 2f;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<PlantHolderComponent, ExaminedEvent>(OnExamine);
|
|
SubscribeLocalEvent<PlantHolderComponent, InteractUsingEvent>(OnInteractUsing);
|
|
SubscribeLocalEvent<PlantHolderComponent, InteractHandEvent>(OnInteractHand);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
var query = EntityQueryEnumerator<PlantHolderComponent>();
|
|
while (query.MoveNext(out var uid, out var plantHolder))
|
|
{
|
|
if (plantHolder.NextUpdate > _gameTiming.CurTime)
|
|
continue;
|
|
plantHolder.NextUpdate = _gameTiming.CurTime + plantHolder.UpdateDelay;
|
|
|
|
Update(uid, plantHolder);
|
|
}
|
|
}
|
|
|
|
private int GetCurrentGrowthStage(Entity<PlantHolderComponent> entity)
|
|
{
|
|
var (uid, component) = entity;
|
|
|
|
if (component.Seed == null)
|
|
return 0;
|
|
|
|
var result = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation));
|
|
return result;
|
|
}
|
|
|
|
private void OnExamine(Entity<PlantHolderComponent> entity, ref ExaminedEvent args)
|
|
{
|
|
if (!args.IsInDetailsRange)
|
|
return;
|
|
|
|
var (_, component) = entity;
|
|
|
|
using (args.PushGroup(nameof(PlantHolderComponent)))
|
|
{
|
|
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(Entity<PlantHolderComponent> entity, ref InteractUsingEvent args)
|
|
{
|
|
var (uid, component) = entity;
|
|
|
|
if (TryComp(args.Used, out SeedComponent? seeds))
|
|
{
|
|
if (component.Seed == null)
|
|
{
|
|
if (!_botany.TryGetSeed(seeds, out var seed))
|
|
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);
|
|
|
|
component.Seed = seed;
|
|
component.Dead = false;
|
|
component.Age = 1;
|
|
if (seeds.HealthOverride != null)
|
|
{
|
|
component.Health = seeds.HealthOverride.Value;
|
|
}
|
|
else
|
|
{
|
|
component.Health = component.Seed.Endurance;
|
|
}
|
|
component.LastCycle = _gameTiming.CurTime;
|
|
|
|
QueueDel(args.Used);
|
|
|
|
CheckLevelSanity(uid, component);
|
|
UpdateSprite(uid, component);
|
|
|
|
return;
|
|
}
|
|
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message",
|
|
("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
|
|
return;
|
|
}
|
|
|
|
if (_tagSystem.HasTag(args.Used, "Hoe"))
|
|
{
|
|
if (component.WeedLevel > 0)
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message",
|
|
("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
|
|
_popup.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message",
|
|
("otherName", Comp<MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User), true);
|
|
component.WeedLevel = 0;
|
|
UpdateSprite(uid, component);
|
|
}
|
|
else
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), args.User);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (HasComp<ShovelComponent>(args.Used))
|
|
{
|
|
if (component.Seed != null)
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message",
|
|
("name", Comp<MetaDataComponent>(uid).EntityName)), args.User, PopupType.Medium);
|
|
_popup.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message",
|
|
("name", Comp<MetaDataComponent>(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<MetaDataComponent>(uid).EntityName)), args.User);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (_solutionContainerSystem.TryGetDrainableSolution(args.Used, out var solution, out _)
|
|
&& _solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution)
|
|
&& 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 = _solutionContainerSystem.Drain(solutionEntity, solution.Value, 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);
|
|
|
|
_solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, split);
|
|
|
|
ForceUpdateByExternalCause(uid, component);
|
|
|
|
return;
|
|
}
|
|
|
|
var highQualitySampleTaker = _tagSystem.HasTag(args.Used, "PlantSampleTakerHighQuality");
|
|
if (_tagSystem.HasTag(args.Used, "PlantSampleTaker") || highQualitySampleTaker)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (GetCurrentGrowthStage(entity) <= 1)
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-early-sample-message"), args.User);
|
|
return;
|
|
}
|
|
|
|
component.Health -= (_random.Next(3, 5) * (highQualitySampleTaker? 3 : 10));
|
|
|
|
float? healthOverride;
|
|
if (component.Harvest)
|
|
{
|
|
healthOverride = null;
|
|
}
|
|
else
|
|
{
|
|
healthOverride = component.Health;
|
|
}
|
|
component.Seed.Unique = false;
|
|
var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, healthOverride);
|
|
_randomHelper.RandomOffset(seed, 0.25f);
|
|
var displayName = Loc.GetString(component.Seed.DisplayName);
|
|
_popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
|
|
("seedName", displayName)), args.User);
|
|
|
|
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<SharpComponent>(args.Used))
|
|
DoHarvest(uid, args.User, component);
|
|
|
|
if (TryComp<ProduceComponent>(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 (_solutionContainerSystem.TryGetSolution(args.Used, produce.SolutionName, out var soln2, out var solution2))
|
|
{
|
|
if (_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, 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);
|
|
_solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, _solutionContainerSystem.SplitSolution(soln2.Value, 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(Entity<PlantHolderComponent> entity, ref InteractHandEvent args)
|
|
{
|
|
DoHarvest(entity, args.User, entity.Comp);
|
|
}
|
|
|
|
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.UpdateSpriteAfterUpdate = true;
|
|
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;
|
|
}
|
|
|
|
// Prevents the plant from aging when lacking resources.
|
|
// Limits the effect on aging so that when resources are added, the plant starts growing in a reasonable amount of time.
|
|
if (component.SkipAging < 10)
|
|
{
|
|
// Make sure the plant is not starving.
|
|
if (component.NutritionLevel > 5)
|
|
{
|
|
component.Health += Convert.ToInt32(_random.Prob(0.35f)) * healthMod;
|
|
}
|
|
else
|
|
{
|
|
AffectGrowth(uid, -1, component);
|
|
component.Health -= healthMod;
|
|
}
|
|
|
|
// Make sure the plant is not thirsty.
|
|
if (component.WaterLevel > 10)
|
|
{
|
|
component.Health += Convert.ToInt32(_random.Prob(0.35f)) * 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);
|
|
return;
|
|
}
|
|
|
|
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<GhostRoleComponent>(uid);
|
|
EnsureComp<GhostTakeoverAvailableComponent>(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<HandsComponent>(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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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.LastProduce = 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 (!_solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution, out var solution))
|
|
return;
|
|
|
|
if (solution.Volume > 0 && component.MutationLevel < 25)
|
|
{
|
|
var amt = FixedPoint2.New(1);
|
|
foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
|
|
{
|
|
var reagentProto = _prototype.Index<ReagentPrototype>(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(ref 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<PointLightComponent>(uid);
|
|
_pointLight.SetRadius(uid, component.Seed.BioluminescentRadius, light);
|
|
_pointLight.SetColor(uid, component.Seed.BioluminescentColor, light);
|
|
_pointLight.SetCastShadows(uid, false, light);
|
|
Dirty(uid, light);
|
|
}
|
|
else
|
|
{
|
|
RemComp<PointLightComponent>(uid);
|
|
}
|
|
|
|
if (!TryComp<AppearanceComponent>(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 = GetCurrentGrowthStage((uid, component));
|
|
|
|
_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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|