using System.Linq; using Content.Client.Chemistry.Containers.EntitySystems; using Content.Shared.Atmos.Prototypes; using Content.Shared.Body.Part; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Kitchen.Components; using Content.Shared.Prototypes; using Robust.Shared.Prototypes; namespace Content.Client.Chemistry.EntitySystems; /// public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem { [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; [ValidatePrototypeId] private const string DefaultMixingCategory = "DummyMix"; [ValidatePrototypeId] private const string DefaultGrindCategory = "DummyGrind"; [ValidatePrototypeId] private const string DefaultJuiceCategory = "DummyJuice"; [ValidatePrototypeId] private const string DefaultCondenseCategory = "DummyCondense"; private readonly Dictionary> _reagentSources = new(); /// public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(OnReceiveRegistryUpdate); SubscribeLocalEvent(OnPrototypesReloaded); OnPrototypesReloaded(null); } private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message) { var data = message.Changeset; foreach (var remove in data.Removed) { Registry.Remove(remove); } foreach (var (key, val) in data.GuideEntries) { Registry[key] = val; } } private void OnPrototypesReloaded(PrototypesReloadedEventArgs? ev) { // this doesn't check what prototypes are being reloaded because, to be frank, we use a lot of them. _reagentSources.Clear(); foreach (var reagent in PrototypeManager.EnumeratePrototypes()) { _reagentSources.Add(reagent.ID, new()); } foreach (var reaction in PrototypeManager.EnumeratePrototypes()) { if (!reaction.Source) continue; var data = new ReagentReactionSourceData( reaction.MixingCategories ?? new () { DefaultMixingCategory }, reaction); foreach (var product in reaction.Products.Keys) { _reagentSources[product].Add(data); } } foreach (var gas in PrototypeManager.EnumeratePrototypes()) { if (gas.Reagent == null) continue; var data = new ReagentGasSourceData( new () { DefaultCondenseCategory }, gas); _reagentSources[gas.Reagent].Add(data); } // store the names of the entities used so we don't get repeats in the guide. var usedNames = new List(); foreach (var entProto in PrototypeManager.EnumeratePrototypes()) { if (entProto.Abstract || usedNames.Contains(entProto.Name)) continue; if (!entProto.TryGetComponent(out var extractableComponent)) continue; //these bloat the hell out of blood/fat if (entProto.HasComponent()) continue; //these feel obvious... if (entProto.HasComponent()) continue; if (extractableComponent.JuiceSolution is { } juiceSolution) { var data = new ReagentEntitySourceData( new() { DefaultJuiceCategory }, entProto, juiceSolution); foreach (var (id, _) in juiceSolution.Contents) { _reagentSources[id.Prototype].Add(data); } usedNames.Add(entProto.Name); } if (extractableComponent.GrindableSolution is { } grindableSolutionId && entProto.TryGetComponent(out var manager) && _solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution)) { var data = new ReagentEntitySourceData( new() { DefaultGrindCategory }, entProto, grindableSolution); foreach (var (id, _) in grindableSolution.Contents) { _reagentSources[id.Prototype].Add(data); } usedNames.Add(entProto.Name); } } } public List GetReagentSources(string id) { return _reagentSources.GetValueOrDefault(id) ?? new List(); } } /// /// A generic class meant to hold information about a reagent source. /// public abstract class ReagentSourceData { /// /// The mixing type that applies to this source. /// public readonly IReadOnlyList> MixingType; /// /// The number of distinct outputs. Used for primary ordering. /// public abstract int OutputCount { get; } /// /// A text string corresponding to this source. Typically a name. Used for secondary ordering. /// public abstract string IdentifierString { get; } protected ReagentSourceData(List> mixingType) { MixingType = mixingType; } } /// /// Used to store a reagent source that's an entity with a corresponding solution. /// public sealed class ReagentEntitySourceData : ReagentSourceData { public readonly EntityPrototype SourceEntProto; public readonly Solution Solution; public override int OutputCount => Solution.Contents.Count; public override string IdentifierString => SourceEntProto.Name; public ReagentEntitySourceData(List> mixingType, EntityPrototype sourceEntProto, Solution solution) : base(mixingType) { SourceEntProto = sourceEntProto; Solution = solution; } } /// /// Used to store a reagent source that comes from a reaction between multiple reagents. /// public sealed class ReagentReactionSourceData : ReagentSourceData { public readonly ReactionPrototype ReactionPrototype; public override int OutputCount => ReactionPrototype.Products.Count + ReactionPrototype.Reactants.Count(r => r.Value.Catalyst); public override string IdentifierString => ReactionPrototype.ID; public ReagentReactionSourceData(List> mixingType, ReactionPrototype reactionPrototype) : base(mixingType) { ReactionPrototype = reactionPrototype; } } /// /// Used to store a reagent source that comes from gas condensation. /// public sealed class ReagentGasSourceData : ReagentSourceData { public readonly GasPrototype GasPrototype; public override int OutputCount => 1; public override string IdentifierString => Loc.GetString(GasPrototype.Name); public ReagentGasSourceData(List> mixingType, GasPrototype gasPrototype) : base(mixingType) { GasPrototype = gasPrototype; } }