using System.Linq;
using Content.Shared.Chemistry.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 SharedSolutionContainerSystem _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, EntityManager.ComponentFactory))
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, EntityManager.ComponentFactory) &&
_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();
}
// Is handled on server and updated on client via ReagentGuideRegistryChangedEvent
public override void ReloadAllReagentPrototypes()
{
}
}
///
/// 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;
}
}