diff --git a/Content.Server/Botany/Components/LogComponent.cs b/Content.Server/Botany/Components/LogComponent.cs index b956d77fcc..b7fbb807c8 100644 --- a/Content.Server/Botany/Components/LogComponent.cs +++ b/Content.Server/Botany/Components/LogComponent.cs @@ -1,37 +1,19 @@ -using System.Threading.Tasks; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Random.Helpers; -using Content.Shared.Tag; +using Content.Server.Botany.Systems; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Botany.Components +namespace Content.Server.Botany.Components; +// TODO: This should probably be merged with SliceableFood somehow or made into a more generic Choppable. + +[RegisterComponent] +[Friend(typeof(LogSystem))] +public sealed class LogComponent : Component { - [RegisterComponent] - public class LogComponent : Component, IInteractUsing - { - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!EntitySystem.Get().CanInteract(eventArgs.User)) - return false; + [DataField("spawnedPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string SpawnedPrototype = "MaterialWoodPlank1"; - var entMan = IoCManager.Resolve(); - - if (eventArgs.Using.HasTag("BotanySharp")) - { - for (var i = 0; i < 2; i++) - { - var plank = entMan.SpawnEntity("MaterialWoodPlank1", entMan.GetComponent(Owner).Coordinates); - plank.RandomOffset(0.25f); - } - - entMan.QueueDeleteEntity(Owner); - - return true; - } - - return false; - } - } + [DataField("spawnCount")] public int SpawnCount = 2; } diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index c7c504c72a..21c8e82bba 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; +using Content.Server.Botany.Systems; using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.Components; using Content.Server.Fluids.Components; @@ -107,7 +108,7 @@ namespace Content.Server.Botany.Components public float WeedCoefficient { get; set; } = 1f; [ViewVariables(VVAccess.ReadWrite)] - public Seed? Seed { get; set; } + public SeedPrototype? Seed { get; set; } [ViewVariables(VVAccess.ReadWrite)] public bool ImproperHeat { get; set; } @@ -146,6 +147,8 @@ namespace Content.Server.Botany.Components _lastCycle = curTime; + // todo ecs. + var botanySystem = EntitySystem.Get(); // Weeds like water and nutrients! They may appear even if there's not a seed planted. if (WaterLevel > 10 && NutritionLevel > 2 && _random.Prob(Seed == null ? 0.05f : 0.01f)) @@ -272,7 +275,7 @@ namespace Content.Server.Botany.Components } } - // Seed pressure resistance. + // SeedPrototype pressure resistance. var pressure = environment.Pressure; if (pressure < Seed.LowPressureTolerance || pressure > Seed.HighPressureTolerance) { @@ -286,7 +289,7 @@ namespace Content.Server.Botany.Components ImproperPressure = false; } - // Seed ideal temperature. + // SeedPrototype ideal temperature. if (MathF.Abs(environment.Temperature - Seed.IdealHeat) > Seed.HeatTolerance) { Health -= healthMod; @@ -359,7 +362,7 @@ namespace Content.Server.Botany.Components } else if (Age < 0) // Revert back to seed packet! { - Seed.SpawnSeedPacket(_entMan.GetComponent(Owner).Coordinates); + botanySystem.SpawnSeedPacket(Seed, _entMan.GetComponent(Owner).Coordinates); RemovePlant(); ForceUpdate = true; Update(); @@ -422,19 +425,21 @@ namespace Content.Server.Botany.Components if (Seed == null || _entMan.Deleted(user) || !EntitySystem.Get().CanInteract(user)) return false; + var botanySystem = EntitySystem.Get(); + if (Harvest && !Dead) { if (_entMan.TryGetComponent(user, out HandsComponent? hands)) { - if (!Seed.CheckHarvest(user, hands.GetActiveHandItem?.Owner)) + if (!botanySystem.CanHarvest(Seed, hands.GetActiveHandItem?.Owner)) return false; } - else if (!Seed.CheckHarvest(user)) + else if (!botanySystem.CanHarvest(Seed)) { return false; } - Seed.Harvest(user, YieldMod); + botanySystem.Harvest(Seed, user, YieldMod); AfterHarvest(); return true; } @@ -451,7 +456,9 @@ namespace Content.Server.Botany.Components if (Seed == null || !Harvest) return; - Seed.AutoHarvest(_entMan.GetComponent(Owner).Coordinates); + var botanySystem = EntitySystem.Get(); + + botanySystem.AutoHarvest(Seed, _entMan.GetComponent(Owner).Coordinates); AfterHarvest(); } @@ -633,7 +640,7 @@ namespace Content.Server.Botany.Components // If this seed is not in the global seed list, then no products of this line have been harvested yet. // It is then safe to assume it's restricted to this tray. if (Seed == null) return; - var plantSystem = EntitySystem.Get(); + var plantSystem = EntitySystem.Get(); if (plantSystem.Seeds.ContainsKey(Seed.Uid)) Seed = Seed.Diverge(modified); } @@ -653,22 +660,21 @@ namespace Content.Server.Botany.Components if ((!_entMan.EntityExists(usingItem) ? EntityLifeStage.Deleted : _entMan.GetComponent(usingItem).EntityLifeStage) >= EntityLifeStage.Deleted || !EntitySystem.Get().CanInteract(user)) return false; + var botanySystem = EntitySystem.Get(); + if (_entMan.TryGetComponent(usingItem, out SeedComponent? seeds)) { if (Seed == null) { - if (seeds.Seed == null) - { - user.PopupMessageCursor(Loc.GetString("plant-holder-component-empty-seed-packet-message")); - _entMan.QueueDeleteEntity(usingItem); + var protoMan = IoCManager.Resolve(); + if (!protoMan.TryIndex(seeds.SeedName, out var seed)) return false; - } user.PopupMessageCursor(Loc.GetString("plant-holder-component-plant-success-message", - ("seedName", seeds.Seed.SeedName), - ("seedNoun", seeds.Seed.SeedNoun))); + ("seedName", seed.SeedName), + ("seedNoun", seed.SeedNoun))); - Seed = seeds.Seed; + Seed = seed; Dead = false; Age = 1; Health = Seed.Endurance; @@ -778,7 +784,7 @@ namespace Content.Server.Botany.Components return false; } - var seed = Seed.SpawnSeedPacket(_entMan.GetComponent(user).Coordinates); + var seed = botanySystem.SpawnSeedPacket(Seed, _entMan.GetComponent(user).Coordinates); seed.RandomOffset(0.25f); user.PopupMessageCursor(Loc.GetString("plant-holder-component-take-sample-message", ("seedName", Seed.DisplayName))); diff --git a/Content.Server/Botany/Components/ProduceComponent.cs b/Content.Server/Botany/Components/ProduceComponent.cs index 206fb53b98..b926429b6f 100644 --- a/Content.Server/Botany/Components/ProduceComponent.cs +++ b/Content.Server/Botany/Components/ProduceComponent.cs @@ -1,62 +1,15 @@ -using Content.Server.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Server.GameObjects; +using Content.Server.Botany.Systems; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; -namespace Content.Server.Botany.Components +namespace Content.Server.Botany.Components; + +[RegisterComponent] +[Friend(typeof(BotanySystem))] +public sealed class ProduceComponent : Component { - [RegisterComponent] - public class ProduceComponent : Component, ISerializationHooks - { - [DataField("targetSolution")] public string SolutionName { get; set; } = "food"; + [DataField("targetSolution")] public string SolutionName { get; set; } = "food"; - [DataField("seed")] private string? _seedName; - - [ViewVariables] - public Seed? Seed - { - get => _seedName != null ? IoCManager.Resolve().Index(_seedName) : null; - set => _seedName = value?.ID; - } - - public float Potency => Seed?.Potency ?? 0; - - public void Grown() - { - if (Seed == null) - return; - - if (IoCManager.Resolve().TryGetComponent(Owner, out SpriteComponent? sprite)) - { - sprite.LayerSetRSI(0, Seed.PlantRsi); - sprite.LayerSetState(0, Seed.PlantIconState); - } - - var solutionContainer = EntitySystem.Get().EnsureSolution(Owner, SolutionName); - if (solutionContainer == null) - { - Logger.Warning($"No solution container found in {nameof(ProduceComponent)}."); - return; - } - - solutionContainer.RemoveAllSolution(); - foreach (var (chem, quantity) in Seed.Chemicals) - { - var amount = FixedPoint2.New(quantity.Min); - if (quantity.PotencyDivisor > 0 && Potency > 0) - amount += FixedPoint2.New(Potency / quantity.PotencyDivisor); - amount = FixedPoint2.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max)); - solutionContainer.MaxVolume += amount; - solutionContainer.AddReagent(chem, amount); - } - } - } + [DataField("seed", required: true)] public string SeedName = default!; } diff --git a/Content.Server/Botany/Components/SeedComponent.cs b/Content.Server/Botany/Components/SeedComponent.cs index cd6205f67b..0dab08a280 100644 --- a/Content.Server/Botany/Components/SeedComponent.cs +++ b/Content.Server/Botany/Components/SeedComponent.cs @@ -1,51 +1,15 @@ -using Content.Shared.Examine; +using Content.Server.Botany.Systems; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Botany.Components { - [RegisterComponent] -#pragma warning disable 618 - public class SeedComponent : Component, IExamine -#pragma warning restore 618 + [RegisterComponent, Friend(typeof(BotanySystem))] + public sealed class SeedComponent : Component { - [DataField("seed")] - private string? _seedName; - - [ViewVariables] - public Seed? Seed - { - get => _seedName != null ? IoCManager.Resolve().Index(_seedName) : null; - set => _seedName = value?.ID; - } - - public void Examine(FormattedMessage message, bool inDetailsRange) - { - if (!inDetailsRange) - return; - - if (Seed == null) - { - message.AddMarkup(Loc.GetString("seed-component-no-seeds-message") + "\n"); - return; - } - - message.AddMarkup(Loc.GetString($"seed-component-description", ("seedName", Seed.DisplayName)) + "\n"); - - if (!Seed.RoundStart) - { - message.AddMarkup(Loc.GetString($"seed-component-has-variety-tag", ("seedUid", Seed.Uid)) + "\n"); - } - else - { - message.AddMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", Seed.Yield)) + "\n"); - message.AddMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", Seed.Potency)) + "\n"); - } - } + [DataField("seed", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string SeedName = default!; } } diff --git a/Content.Server/Botany/Components/SeedExtractorComponent.cs b/Content.Server/Botany/Components/SeedExtractorComponent.cs index bfdc0fc455..ed4546d7ce 100644 --- a/Content.Server/Botany/Components/SeedExtractorComponent.cs +++ b/Content.Server/Botany/Components/SeedExtractorComponent.cs @@ -1,46 +1,16 @@ -using System.Threading.Tasks; -using Content.Server.Power.Components; -using Content.Shared.Interaction; -using Content.Shared.Popups; +using Content.Server.Botany.Systems; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager.Attributes; -namespace Content.Server.Botany.Components +namespace Content.Server.Botany.Components; + +[RegisterComponent] +[Friend(typeof(SeedExtractorSystem))] +public sealed class SeedExtractorComponent : Component { - [RegisterComponent] - public class SeedExtractorComponent : Component, IInteractUsing - { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; + // TODO: Upgradeable machines. + [DataField("minSeeds")] public int MinSeeds = 1; - // TODO: Upgradeable machines. - private int _minSeeds = 1; - private int _maxSeeds = 4; - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!_entMan.TryGetComponent(Owner, out var powerReceiverComponent) || !powerReceiverComponent.Powered) - return false; - - if (_entMan.TryGetComponent(eventArgs.Using, out ProduceComponent? produce) && produce.Seed != null) - { - eventArgs.User.PopupMessageCursor(Loc.GetString("seed-extractor-component-interact-message",("name", _entMan.GetComponent(eventArgs.Using).EntityName))); - - _entMan.QueueDeleteEntity(eventArgs.Using); - - var random = _random.Next(_minSeeds, _maxSeeds); - - for (var i = 0; i < random; i++) - { - produce.Seed.SpawnSeedPacket(_entMan.GetComponent(Owner).Coordinates, _entMan); - } - - return true; - } - - return false; - } - } + [DataField("maxSeeds")] public int MaxSeeds = 4; } diff --git a/Content.Server/Botany/Seed.cs b/Content.Server/Botany/Seed.cs deleted file mode 100644 index 59af1dec68..0000000000 --- a/Content.Server/Botany/Seed.cs +++ /dev/null @@ -1,362 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Content.Server.Botany.Components; -using Content.Server.Plants; -using Content.Shared.Atmos; -using Content.Shared.Popups; -using Content.Shared.Random.Helpers; -using Content.Shared.Tag; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; -using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Botany -{ - public enum HarvestType : byte - { - NoRepeat, - Repeat, - SelfHarvest, - } - - /* - public enum PlantSpread : byte - { - NoSpread, - Creepers, - Vines, - } - - public enum PlantMutation : byte - { - NoMutation, - Mutable, - HighlyMutable, - } - - public enum PlantCarnivorous : byte - { - NotCarnivorous, - EatPests, - EatLivingBeings, - } - - public enum PlantJuicy : byte - { - NotJuicy, - Juicy, - Slippery, - } - */ - - [DataDefinition] - public struct SeedChemQuantity - { - [DataField("Min")] - public int Min; - [DataField("Max")] - public int Max; - [DataField("PotencyDivisor")] - public int PotencyDivisor; - } - - [Prototype("seed")] - public class Seed : IPrototype - { - private const string SeedPrototype = "SeedBase"; - - [ViewVariables] - [DataField("id", required: true)] - public string ID { get; private init; } = default!; - - /// - /// Unique identifier of this seed. Do NOT set this. - /// - public int Uid { get; internal set; } = -1; - - #region Tracking - - [ViewVariables] [DataField("name")] public string Name { get; set; } = string.Empty; - [ViewVariables] [DataField("seedName")] public string SeedName { get; set; } = string.Empty; - - [ViewVariables] - [DataField("seedNoun")] - public string SeedNoun { get; set; } = "seeds"; - [ViewVariables] [DataField("displayName")] public string DisplayName { get; set; } = string.Empty; - - [ViewVariables] - [DataField("roundStart")] - public bool RoundStart { get; private set; } = true; - [ViewVariables] [DataField("mysterious")] public bool Mysterious { get; set; } - [ViewVariables] [DataField("immutable")] public bool Immutable { get; set; } - #endregion - - #region Output - - [ViewVariables] - [DataField("productPrototypes", customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List ProductPrototypes { get; set; } = new(); - - [ViewVariables] - [DataField("chemicals")] - public Dictionary Chemicals { get; set; } = new(); - - [ViewVariables] - [DataField("consumeGasses")] - public Dictionary ConsumeGasses { get; set; } = new(); - - [ViewVariables] - [DataField("exudeGasses")] - public Dictionary ExudeGasses { get; set; } = new(); - #endregion - - #region Tolerances - - [ViewVariables] - [DataField("nutrientConsumption")] - public float NutrientConsumption { get; set; } = 0.25f; - - [ViewVariables] [DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f; - [ViewVariables] [DataField("idealHeat")] public float IdealHeat { get; set; } = 293f; - [ViewVariables] [DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f; - [ViewVariables] [DataField("idealLight")] public float IdealLight { get; set; } = 7f; - [ViewVariables] [DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f; - [ViewVariables] [DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f; - - [ViewVariables] - [DataField("lowPressureTolerance")] - public float LowPressureTolerance { get; set; } = 25f; - - [ViewVariables] - [DataField("highPressureTolerance")] - public float HighPressureTolerance { get; set; } = 200f; - - [ViewVariables] - [DataField("pestTolerance")] - public float PestTolerance { get; set; } = 5f; - - [ViewVariables] - [DataField("weedTolerance")] - public float WeedTolerance { get; set; } = 5f; - #endregion - - #region General traits - - [ViewVariables] - [DataField("endurance")] - public float Endurance { get; set; } = 100f; - [ViewVariables] [DataField("yield")] public int Yield { get; set; } - [ViewVariables] [DataField("lifespan")] public float Lifespan { get; set; } - [ViewVariables] [DataField("maturation")] public float Maturation { get; set; } - [ViewVariables] [DataField("production")] public float Production { get; set; } - [ViewVariables] [DataField("growthStages")] public int GrowthStages { get; set; } = 6; - [ViewVariables] [DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat; - - [ViewVariables] [DataField("potency")] public float Potency { get; set; } = 1f; - // No, I'm not removing these. - //public PlantSpread Spread { get; set; } - //public PlantMutation Mutation { get; set; } - //public float AlterTemperature { get; set; } - //public PlantCarnivorous Carnivorous { get; set; } - //public bool Parasite { get; set; } - //public bool Hematophage { get; set; } - //public bool Thorny { get; set; } - //public bool Stinging { get; set; } - [DataField("ligneous")] - public bool Ligneous { get; set; } - // public bool Teleporting { get; set; } - // public PlantJuicy Juicy { get; set; } - #endregion - - #region Cosmetics - - [ViewVariables] - [DataField("plantRsi", required: true)] - public ResourcePath PlantRsi { get; set; } = default!; - - [ViewVariables] - [DataField("plantIconState")] - public string PlantIconState { get; set; } = "produce"; - - [ViewVariables] - [DataField("bioluminescent")] - public bool Bioluminescent { get; set; } - - [ViewVariables] - [DataField("bioluminescentColor")] - public Color BioluminescentColor { get; set; } = Color.White; - - [ViewVariables] - [DataField("splatPrototype")] - public string? SplatPrototype { get; set; } - - #endregion - - public Seed Clone() - { - var newSeed = new Seed() - { - ID = ID, - Name = Name, - SeedName = SeedName, - SeedNoun = SeedNoun, - RoundStart = RoundStart, - Mysterious = Mysterious, - - ProductPrototypes = new List(ProductPrototypes), - Chemicals = new Dictionary(Chemicals), - ConsumeGasses = new Dictionary(ConsumeGasses), - ExudeGasses = new Dictionary(ExudeGasses), - - NutrientConsumption = NutrientConsumption, - WaterConsumption = WaterConsumption, - IdealHeat = IdealHeat, - HeatTolerance = HeatTolerance, - IdealLight = IdealLight, - LightTolerance = LightTolerance, - ToxinsTolerance = ToxinsTolerance, - LowPressureTolerance = LowPressureTolerance, - HighPressureTolerance = HighPressureTolerance, - PestTolerance = PestTolerance, - WeedTolerance = WeedTolerance, - - Endurance = Endurance, - Yield = Yield, - Lifespan = Lifespan, - Maturation = Maturation, - Production = Production, - GrowthStages = GrowthStages, - HarvestRepeat = HarvestRepeat, - Potency = Potency, - - PlantRsi = PlantRsi, - PlantIconState = PlantIconState, - Bioluminescent = Bioluminescent, - BioluminescentColor = BioluminescentColor, - SplatPrototype = SplatPrototype, - }; - - return newSeed; - } - - public EntityUid SpawnSeedPacket(EntityCoordinates transformCoordinates, IEntityManager? entityManager = null) - { - entityManager ??= IoCManager.Resolve(); - - var seed = entityManager.SpawnEntity(SeedPrototype, transformCoordinates); - - var seedComp = seed.EnsureComponent(); - seedComp.Seed = this; - - if (entityManager.TryGetComponent(seed, out SpriteComponent? sprite)) - { - // Seed state will always be seed. Blame the spriter if that's not the case! - sprite.LayerSetSprite(0, new SpriteSpecifier.Rsi(PlantRsi, "seed")); - } - - string val = Loc.GetString("botany-seed-packet-name", ("seedName", SeedName), ("seedNoun", SeedNoun)); - entityManager.GetComponent(seed).EntityName = val; - - return seed; - } - - private void AddToDatabase() - { - var plantSystem = EntitySystem.Get(); - if (plantSystem.AddSeedToDatabase(this)) - { - Name = Uid.ToString(); - } - } - - public IEnumerable AutoHarvest(EntityCoordinates position, int yieldMod = 1) - { - if (position.IsValid(IoCManager.Resolve()) && ProductPrototypes != null && - ProductPrototypes.Count > 0) - return GenerateProduct(position, yieldMod); - - return Enumerable.Empty(); - } - - public IEnumerable Harvest(EntityUid user, int yieldMod = 1) - { - AddToDatabase(); - - if (ProductPrototypes == null || ProductPrototypes.Count == 0 || Yield <= 0) - { - user.PopupMessageCursor(Loc.GetString("botany-harvest-fail-message")); - return Enumerable.Empty(); - } - - user.PopupMessageCursor(Loc.GetString("botany-harvest-success-message", ("name", DisplayName))); - return GenerateProduct(IoCManager.Resolve().GetComponent(user).Coordinates, yieldMod); - } - - public IEnumerable GenerateProduct(EntityCoordinates position, int yieldMod = 1) - { - var totalYield = 0; - if (Yield > -1) - { - if (yieldMod < 0) - { - yieldMod = 1; - totalYield = Yield; - } - else - { - totalYield = Yield * yieldMod; - } - - totalYield = Math.Max(1, totalYield); - } - - var random = IoCManager.Resolve(); - var entityManager = IoCManager.Resolve(); - - var products = new List(); - - for (var i = 0; i < totalYield; i++) - { - var product = random.Pick(ProductPrototypes); - - var entity = entityManager.SpawnEntity(product, position); - entity.RandomOffset(0.25f); - products.Add(entity); - - var produce = entity.EnsureComponent(); - - produce.Seed = this; - produce.Grown(); - - if (Mysterious) - { - var metaData = entityManager.GetComponent(entity); - metaData.EntityName += "?"; - metaData.EntityDescription += (" " + Loc.GetString("botany-mysterious-description-addon")); - } - } - - return products; - } - - public Seed Diverge(bool modified) - { - return Clone(); - } - - public bool CheckHarvest(EntityUid user, EntityUid? held = null) - { - return !Ligneous || (Ligneous && held != null && held.Value.HasTag("BotanySharp")); - } - } -} diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs new file mode 100644 index 0000000000..2855446d6e --- /dev/null +++ b/Content.Server/Botany/SeedPrototype.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.Atmos; +using Content.Shared.Popups; +using Content.Shared.Random.Helpers; +using Content.Shared.Tag; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Utility; + +namespace Content.Server.Botany; + +public enum HarvestType : byte +{ + NoRepeat, + Repeat, + SelfHarvest +} + +/* + public enum PlantSpread : byte + { + NoSpread, + Creepers, + Vines, + } + + public enum PlantMutation : byte + { + NoMutation, + Mutable, + HighlyMutable, + } + + public enum PlantCarnivorous : byte + { + NotCarnivorous, + EatPests, + EatLivingBeings, + } + + public enum PlantJuicy : byte + { + NotJuicy, + Juicy, + Slippery, + } +*/ + +[DataDefinition] +public struct SeedChemQuantity +{ + [DataField("Min")] public int Min; + [DataField("Max")] public int Max; + [DataField("PotencyDivisor")] public int PotencyDivisor; +} + +[Prototype("seed")] +public sealed class SeedPrototype : IPrototype +{ + public const string Prototype = "SeedBase"; + + [DataField("id", required: true)] public string ID { get; private init; } = default!; + + /// + /// Unique identifier of this seed. Do NOT set this. + /// + public int Uid { get; internal set; } = -1; + + #region Tracking + + [DataField("name")] public string Name = string.Empty; + [DataField("seedName")] public string SeedName = string.Empty; + [DataField("seedNoun")] public string SeedNoun = "seeds"; + [DataField("displayName")] public string DisplayName = string.Empty; + + [DataField("roundStart")] public bool RoundStart = true; + [DataField("mysterious")] public bool Mysterious; + [DataField("immutable")] public bool Immutable; + + #endregion + + #region Output + + [DataField("productPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List ProductPrototypes = new(); + + [DataField("chemicals")] public Dictionary Chemicals = new(); + + [DataField("consumeGasses")] public Dictionary ConsumeGasses = new(); + + [DataField("exudeGasses")] public Dictionary ExudeGasses = new(); + + #endregion + + #region Tolerances + + [DataField("nutrientConsumption")] public float NutrientConsumption = 0.25f; + + [DataField("waterConsumption")] public float WaterConsumption = 3f; + [DataField("idealHeat")] public float IdealHeat = 293f; + [DataField("heatTolerance")] public float HeatTolerance = 20f; + [DataField("idealLight")] public float IdealLight = 7f; + [DataField("lightTolerance")] public float LightTolerance = 5f; + [DataField("toxinsTolerance")] public float ToxinsTolerance = 4f; + + [DataField("lowPressureTolerance")] public float LowPressureTolerance = 25f; + + [DataField("highPressureTolerance")] public float HighPressureTolerance = 200f; + + [DataField("pestTolerance")] public float PestTolerance = 5f; + + [DataField("weedTolerance")] public float WeedTolerance = 5f; + + #endregion + + #region General traits + + [DataField("endurance")] public float Endurance = 100f; + + [DataField("yield")] public int Yield; + [DataField("lifespan")] public float Lifespan; + [DataField("maturation")] public float Maturation; + [DataField("production")] public float Production; + [DataField("growthStages")] public int GrowthStages = 6; + [DataField("harvestRepeat")] public HarvestType HarvestRepeat = HarvestType.NoRepeat; + + [DataField("potency")] public float Potency = 1f; + + // No, I'm not removing these. + //public PlantSpread Spread { get; set; } + //public PlantMutation Mutation { get; set; } + //public float AlterTemperature { get; set; } + //public PlantCarnivorous Carnivorous { get; set; } + //public bool Parasite { get; set; } + //public bool Hematophage { get; set; } + //public bool Thorny { get; set; } + //public bool Stinging { get; set; } + + [DataField("ligneous")] public bool Ligneous; + // public bool Teleporting { get; set; } + // public PlantJuicy Juicy { get; set; } + + #endregion + + #region Cosmetics + + [DataField("plantRsi", required: true)] + public ResourcePath PlantRsi { get; set; } = default!; + + [DataField("plantIconState")] public string PlantIconState { get; set; } = "produce"; + + [DataField("bioluminescent")] public bool Bioluminescent { get; set; } + + [DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White; + + [DataField("splatPrototype")] public string? SplatPrototype { get; set; } + + #endregion + + public SeedPrototype Clone() + { + var newSeed = new SeedPrototype + { + ID = ID, + Name = Name, + SeedName = SeedName, + SeedNoun = SeedNoun, + RoundStart = RoundStart, + Mysterious = Mysterious, + + ProductPrototypes = new List(ProductPrototypes), + Chemicals = new Dictionary(Chemicals), + ConsumeGasses = new Dictionary(ConsumeGasses), + ExudeGasses = new Dictionary(ExudeGasses), + + NutrientConsumption = NutrientConsumption, + WaterConsumption = WaterConsumption, + IdealHeat = IdealHeat, + HeatTolerance = HeatTolerance, + IdealLight = IdealLight, + LightTolerance = LightTolerance, + ToxinsTolerance = ToxinsTolerance, + LowPressureTolerance = LowPressureTolerance, + HighPressureTolerance = HighPressureTolerance, + PestTolerance = PestTolerance, + WeedTolerance = WeedTolerance, + + Endurance = Endurance, + Yield = Yield, + Lifespan = Lifespan, + Maturation = Maturation, + Production = Production, + GrowthStages = GrowthStages, + HarvestRepeat = HarvestRepeat, + Potency = Potency, + + PlantRsi = PlantRsi, + PlantIconState = PlantIconState, + Bioluminescent = Bioluminescent, + BioluminescentColor = BioluminescentColor, + SplatPrototype = SplatPrototype + }; + + return newSeed; + } + + public SeedPrototype Diverge(bool modified) + { + return Clone(); + } +} diff --git a/Content.Server/Plants/PlantSystem.cs b/Content.Server/Botany/Systems/BotanySystem.Plant.cs similarity index 70% rename from Content.Server/Plants/PlantSystem.cs rename to Content.Server/Botany/Systems/BotanySystem.Plant.cs index d2365afc17..db7981980e 100644 --- a/Content.Server/Plants/PlantSystem.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Plant.cs @@ -1,25 +1,28 @@ using System.Collections.Generic; -using Content.Server.Botany; using Content.Server.Botany.Components; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Popups; using Content.Shared.GameTicking; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Prototypes; +using Robust.Shared.Random; -namespace Content.Server.Plants +namespace Content.Server.Botany.Systems { [UsedImplicitly] - public class PlantSystem : EntitySystem + public sealed partial class BotanySystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; private int _nextUid = 0; - private readonly Dictionary _seeds = new(); - private float _timer = 0f; - public IReadOnlyDictionary Seeds => _seeds; + public readonly Dictionary Seeds = new(); public override void Initialize() { @@ -27,6 +30,8 @@ namespace Content.Server.Plants SubscribeLocalEvent(Reset); + InitializeSeeds(); + PopulateDatabase(); } @@ -34,22 +39,22 @@ namespace Content.Server.Plants { _nextUid = 0; - _seeds.Clear(); + Seeds.Clear(); - foreach (var seed in _prototypeManager.EnumeratePrototypes()) + foreach (var seed in _prototypeManager.EnumeratePrototypes()) { AddSeedToDatabase(seed); } } - public bool AddSeedToDatabase(Seed seed) + public bool AddSeedToDatabase(SeedPrototype seed) { // If it's not -1, it's already in the database. Probably. if (seed.Uid != -1) return false; seed.Uid = GetNextSeedUid(); - _seeds[seed.Uid] = seed; + Seeds[seed.Uid] = seed; return true; } diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs new file mode 100644 index 0000000000..d4bfc2d0c9 --- /dev/null +++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs @@ -0,0 +1,35 @@ +using Content.Server.Botany.Components; +using Content.Shared.FixedPoint; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Content.Server.Botany.Systems; + +public partial class BotanySystem +{ + public void ProduceGrown(EntityUid uid, ProduceComponent produce) + { + if (!_prototypeManager.TryIndex(produce.SeedName, out var seed)) + return; + + if (TryComp(uid, out SpriteComponent? sprite)) + { + sprite.LayerSetRSI(0, seed.PlantRsi); + sprite.LayerSetState(0, seed.PlantIconState); + } + + var solutionContainer = _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName); + + solutionContainer.RemoveAllSolution(); + foreach (var (chem, quantity) in seed.Chemicals) + { + var amount = FixedPoint2.New(quantity.Min); + if (quantity.PotencyDivisor > 0 && seed.Potency > 0) + amount += FixedPoint2.New(seed.Potency / quantity.PotencyDivisor); + amount = FixedPoint2.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max)); + solutionContainer.MaxVolume += amount; + solutionContainer.AddReagent(chem, amount); + } + } +} diff --git a/Content.Server/Botany/Systems/BotanySystem.Seed.cs b/Content.Server/Botany/Systems/BotanySystem.Seed.cs new file mode 100644 index 0000000000..f32a49a45f --- /dev/null +++ b/Content.Server/Botany/Systems/BotanySystem.Seed.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Botany.Components; +using Content.Shared.Examine; +using Content.Shared.Random.Helpers; +using Content.Shared.Tag; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Botany.Systems; + +public partial class BotanySystem +{ + public void InitializeSeeds() + { + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, SeedComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + if (!_prototypeManager.TryIndex(component.SeedName, out var seed)) + return; + + args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", seed.DisplayName))); + + if (!seed.RoundStart) + { + args.PushMarkup(Loc.GetString($"seed-component-has-variety-tag", ("seedUid", seed.Uid))); + } + else + { + args.PushMarkup(Loc.GetString($"seed-component-plant-yield-text", ("seedYield", seed.Yield))); + args.PushMarkup(Loc.GetString($"seed-component-plant-potency-text", ("seedPotency", seed.Potency))); + } + } + + #region SeedPrototype prototype stuff + + public EntityUid SpawnSeedPacket(SeedPrototype proto, EntityCoordinates transformCoordinates) + { + var seed = Spawn(SeedPrototype.Prototype, transformCoordinates); + + var seedComp = EnsureComp(seed); + seedComp.SeedName = proto.ID; + + if (TryComp(seed, out SpriteComponent? sprite)) + { + // TODO visualizer + // SeedPrototype state will always be seed. Blame the spriter if that's not the case! + sprite.LayerSetSprite(0, new SpriteSpecifier.Rsi(proto.PlantRsi, "seed")); + } + + string val = Loc.GetString("botany-seed-packet-name", ("seedName", proto.SeedName), ("seedNoun", proto.SeedNoun)); + MetaData(seed).EntityName = val; + + return seed; + } + + public IEnumerable AutoHarvest(SeedPrototype proto, EntityCoordinates position, int yieldMod = 1) + { + if (position.IsValid(EntityManager) && + proto.ProductPrototypes.Count > 0) + return GenerateProduct(proto, position, yieldMod); + + return Enumerable.Empty(); + } + + public IEnumerable Harvest(SeedPrototype proto, EntityUid user, int yieldMod = 1) + { + if (AddSeedToDatabase(proto)) proto.Name = proto.Uid.ToString(); + + if (proto.ProductPrototypes.Count == 0 || proto.Yield <= 0) + { + _popupSystem.PopupCursor(Loc.GetString("botany-harvest-fail-message"), + Filter.Entities(user)); + return Enumerable.Empty(); + } + + _popupSystem.PopupCursor(Loc.GetString("botany-harvest-success-message", ("name", proto.DisplayName)), + Filter.Entities(user)); + return GenerateProduct(proto, Transform(user).Coordinates, yieldMod); + } + + public IEnumerable GenerateProduct(SeedPrototype proto, EntityCoordinates position, int yieldMod = 1) + { + var totalYield = 0; + if (proto.Yield > -1) + { + if (yieldMod < 0) + { + yieldMod = 1; + totalYield = proto.Yield; + } + else + totalYield = proto.Yield * yieldMod; + + totalYield = Math.Max(1, totalYield); + } + + var products = new List(); + + for (var i = 0; i < totalYield; i++) + { + var product = _robustRandom.Pick(proto.ProductPrototypes); + + var entity = Spawn(product, position); + entity.RandomOffset(0.25f); + products.Add(entity); + + var produce = EnsureComp(entity); + + produce.SeedName = proto.ID; + ProduceGrown(entity, produce); + + if (proto.Mysterious) + { + var metaData = MetaData(entity); + metaData.EntityName += "?"; + metaData.EntityDescription += " " + Loc.GetString("botany-mysterious-description-addon"); + } + } + + return products; + } + + public bool CanHarvest(SeedPrototype proto, EntityUid? held = null) + { + return !proto.Ligneous || proto.Ligneous && held != null && held.Value.HasTag("BotanySharp"); + } + + #endregion +} diff --git a/Content.Server/Botany/Systems/LogSystem.cs b/Content.Server/Botany/Systems/LogSystem.cs new file mode 100644 index 0000000000..ba859b4ea7 --- /dev/null +++ b/Content.Server/Botany/Systems/LogSystem.cs @@ -0,0 +1,32 @@ +using Content.Server.Botany.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Interaction; +using Content.Shared.Random.Helpers; +using Content.Shared.Tag; +using Robust.Shared.GameObjects; + +namespace Content.Server.Botany.Systems; + +public sealed class LogSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, LogComponent component, InteractUsingEvent args) + { + if (args.Used.HasTag("BotanySharp")) + { + for (var i = 0; i < component.SpawnCount; i++) + { + var plank = Spawn(component.SpawnedPrototype, Transform(uid).Coordinates); + plank.RandomOffset(0.25f); + } + + QueueDel(uid); + } + } +} diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs new file mode 100644 index 0000000000..16c9d40972 --- /dev/null +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -0,0 +1,52 @@ +using Content.Server.Botany.Components; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Shared.Interaction; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Botany.Systems; + +public sealed class SeedExtractorSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly BotanySystem _botanySystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, SeedExtractorComponent component, InteractUsingEvent args) + { + if (!TryComp(uid, out var powerReceiverComponent) || !powerReceiverComponent.Powered) + return; + + if (TryComp(args.Used, out ProduceComponent? produce)) + { + if (!_prototypeManager.TryIndex(produce.SeedName, out var seed)) + return; + + _popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-interact-message",("name", args.Used)), + Filter.Entities(args.User)); + + QueueDel(args.Used); + + var random = _random.Next(component.MinSeeds, component.MaxSeeds); + var coords = Transform(uid).Coordinates; + + for (var i = 0; i < random; i++) + { + _botanySystem.SpawnSeedPacket(seed, coords); + } + } + } +} diff --git a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl index 8f2e070711..85934f2418 100644 --- a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl +++ b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl @@ -1,6 +1,5 @@ ## Entity -plant-holder-component-empty-seed-packet-message = The packet seems to be empty. You throw it away. plant-holder-component-plant-success-message = You plant the {$seedName} {$seedNoun}. plant-holder-component-already-seeded-message = The {$name} already has seeds in it! plant-holder-component-remove-weeds-message = You remove the weeds from the {$name}. @@ -32,4 +31,4 @@ plant-holder-component-toxins-high-warning = The [color=red]toxicity level alert plant-holder-component-light-improper-warning = The [color=yellow]improper light level alert[/color] is blinking. plant-holder-component-heat-improper-warning = The [color=orange]improper temperature level alert[/color] is blinking. plant-holder-component-pressure-improper-warning = The [color=lightblue]improper environment pressure alert[/color] is blinking. -plant-holder-component-gas-missing-warning = The [color=cyan]improper gas environment alert[/color] is blinking. \ No newline at end of file +plant-holder-component-gas-missing-warning = The [color=cyan]improper gas environment alert[/color] is blinking. diff --git a/Resources/Locale/en-US/botany/components/seed-component.ftl b/Resources/Locale/en-US/botany/components/seed-component.ftl index 74434e0a9a..eace4c1d9c 100644 --- a/Resources/Locale/en-US/botany/components/seed-component.ftl +++ b/Resources/Locale/en-US/botany/components/seed-component.ftl @@ -1,6 +1,5 @@ ## Entity -seed-component-no-seeds-message = It doesn't seem to contain any seeds. seed-component-description = It has a picture of [color=yellow]{$seedName}[/color] on the front. seed-component-has-variety-tag = It's tagged as variety [color=lightgray]no. {$seedUid}[/color]. seed-component-plant-yield-text = Plant Yield: [color=lightblue]{$seedYield}[/color] @@ -9,4 +8,4 @@ seed-component-plant-potency-text = Plant Potency: [color=lightblue]{$seedPotenc botany-seed-packet-name = packet of {$seedName} {$seedNoun} botany-harvest-fail-message = You fail to harvest anything useful. botany-harvest-success-message = You harvest from the {$name} -botany-mysterious-description-addon = On second thought, something about this one looks strange. \ No newline at end of file +botany-mysterious-description-addon = On second thought, something about this one looks strange.