using System.Diagnostics.CodeAnalysis; using Content.Shared.Botany.Components; using Content.Shared.Botany.Items.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Cloning; using Content.Shared.Examine; using Content.Shared.Hands.EntitySystems; using Content.Shared.Random; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Shared.Botany.Systems; public sealed partial class BotanySystem : EntitySystem { [Dependency] private IGameTiming _timing = default!; [Dependency] private MetaDataSystem _metaData = default!; [Dependency] private PlantSystem _plant = default!; [Dependency] private RandomHelperSystem _randomHelper = default!; [Dependency] private SharedAppearanceSystem _appearance = default!; [Dependency] private SharedCloningSystem _cloning = default!; [Dependency] private SharedHandsSystem _hands = default!; [Dependency] private SharedSolutionContainerSystem _solutionContainer = default!; [Dependency] private SharedTransformSystem _transform = default!; public readonly ProtoId SettingsId = "PlantClone"; public readonly ProtoId LifecycleSettingsId = "PlantLifecycleClone"; [SubscribeLocalEvent] private void OnExamined(Entity ent, ref ExaminedEvent args) { if (!args.IsInDetailsRange) return; if (!TryGetPlantComponent(ent.Comp.PlantData, ent.Comp.PlantProtoId, out var plant) || !TryGetPlantComponent(ent.Comp.PlantData, ent.Comp.PlantProtoId, out var plantData)) return; using (args.PushGroup(nameof(SeedComponent), 1)) { var name = Loc.GetString(plantData.Name); args.PushMarkup(Loc.GetString("seed-component-description", ("seedName", name))); args.PushMarkup(_plant.GetPlantStateMarkup(ent.Owner, plant)); } } [SubscribeLocalEvent] private void OnSeedShutdown(Entity ent, ref ComponentShutdown args) { DeletePlantSnapshot(ent.Comp.PlantData); } [SubscribeLocalEvent] private void OnProduceShutdown(Entity ent, ref ComponentShutdown args) { DeletePlantSnapshot(ent.Comp.PlantData); } [SubscribeLocalEvent] private void OnSwabShutdown(Entity ent, ref ComponentShutdown args) { DeletePlantSnapshot(ent.Comp.PlantData); } /// /// Tries to get a plant component from a snapshot or prototype. /// /// The type of component to get. /// The snapshot to get the component from. /// The prototype ID to get the component from. /// The plant component if found. [PublicAPI] public bool TryGetPlantComponent(EntityUid? snapshot, EntProtoId? plantProtoId, [NotNullWhen(true)] out T? plant) where T : class, IComponent, new() { plant = null; if (snapshot != null && TryComp(snapshot, out plant)) return true; if (plantProtoId == null) return false; if (!ProtoMan.TryIndex(plantProtoId.Value, out var proto)) return false; return proto.TryComp(out plant, Factory); } /// /// Clones a component snapshot of a plant. /// /// The entity to clone the snapshot from. /// The entity that should own the snapshot, if any. /// If true, also clone lifecycle state into the snapshot. [PublicAPI] public EntityUid? ClonePlantSnapshotData(EntityUid source, EntityUid? parent = null, bool cloneLifecycle = false) { var settingsId = cloneLifecycle ? LifecycleSettingsId : SettingsId; if (!ProtoMan.TryIndex(settingsId, out var settings)) return null; var snapshot = EntityManager.CreateEntityUninitialized(null); _cloning.CloneComponents(source, snapshot, settings); EntityManager.InitializeAndStartEntity(snapshot, doMapInit: false); if (parent is { } parentUid) _transform.SetParent(snapshot, parentUid); return snapshot; } /// /// Deletes a stored plant snapshot, if one exists. /// [PublicAPI] public void DeletePlantSnapshot(EntityUid? snapshot) { if (snapshot == null) return; PredictedQueueDel(snapshot.Value); } /// /// Applies the component data stored in a plant snapshot to a target entity. /// /// The snapshot entity to copy component data from. /// The entity to apply the snapshot data to. /// If true, also copy lifecycle state. [PublicAPI] public void ApplyPlantSnapshotData(EntityUid? snapshot, EntityUid target, bool cloneLifecycle = false) { if (snapshot == null) return; var settingsId = cloneLifecycle ? LifecycleSettingsId : SettingsId; if (!ProtoMan.TryIndex(settingsId, out var settings)) return; _cloning.CloneComponents(snapshot.Value, target, settings); } /// /// Internal method to spawn a seed packet from a plant component. /// /// The plant data component to spawn. /// The plant prototype ID to store in the seed component. /// The component snapshot to store in the seed component. /// The coordinates to spawn the seed packet at. /// The user who is spawning the seed packet. /// The health override to store in the seed component. /// The spawned seed packet entity. [PublicAPI] public EntityUid SpawnSeedPacket( PlantDataComponent plantData, EntProtoId plantProtoId, EntityUid? snapshot, EntityCoordinates coords, EntityUid user, float? healthOverride = null) { var seedItem = PredictedSpawnAtPosition(plantData.PacketPrototype, coords); var seedComp = EnsureComp(seedItem); seedComp.PlantProtoId = plantProtoId; seedComp.PlantData = snapshot.HasValue ? ClonePlantSnapshotData(snapshot.Value, parent: seedItem) : null; seedComp.HealthOverride = healthOverride; Dirty(seedItem, seedComp); var name = Loc.GetString(plantData.Name); var noun = Loc.GetString(plantData.Noun); _metaData.SetEntityName(seedItem, Loc.GetString("botany-seed-packet-name", ("seedName", name), ("seedNoun", noun))); _hands.TryPickupAnyHand(user, seedItem); return seedItem; } /// /// Reverts a planted plant back into a seed packet while preserving its genetics. /// [PublicAPI] public bool TryRevertPlantToSeed(Entity ent) { if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false)) return false; if (!_plant.TryGetTray(ent.Owner, out var tray)) return false; if (!ProtoMan.Resolve(ent.Comp2.PacketPrototype, out var seedProto) || !seedProto.TryComp(out var seedComp, Factory)) { return false; } SpawnSeedPacket( ent.Comp2, seedComp.PlantProtoId, ent.Owner, Transform(tray).Coordinates, tray.Owner); _plant.RemovePlant(ent.Owner); return true; } }