using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Lathe.Prototypes;
using Content.Shared.Localizations;
using Content.Shared.Materials;
using Content.Shared.Research.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Lathe;
///
/// This handles...
///
public abstract partial class SharedLatheSystem : EntitySystem
{
[Dependency] private SharedMaterialStorageSystem _materialStorage = default!;
[Dependency] private EmagSystem _emag = default!;
public readonly Dictionary> InverseRecipes = new();
public const int MaxItemsPerRequest = 10_000;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnEmagged);
SubscribeLocalEvent(OnExamined);
SubscribeLocalEvent(OnPrototypesReloaded);
BuildInverseRecipeDictionary();
}
///
/// Get the set of all recipes that a lathe could possibly ever create (e.g., if all techs were unlocked).
///
public HashSet> GetAllPossibleRecipes(LatheComponent component)
{
var recipes = new HashSet>();
foreach (var pack in component.StaticPacks)
{
recipes.UnionWith(ProtoMan.Index(pack).Recipes);
}
foreach (var pack in component.DynamicPacks)
{
recipes.UnionWith(ProtoMan.Index(pack).Recipes);
}
return recipes;
}
///
/// Add every recipe in the list of recipe packs to a single hashset.
///
public void AddRecipesFromPacks(HashSet> recipes, IEnumerable> packs)
{
foreach (var id in packs)
{
var pack = ProtoMan.Index(id);
recipes.UnionWith(pack.Recipes);
}
}
private void OnExamined(Entity ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
if (ent.Comp.ReagentOutputSlotId != null)
args.PushMarkup(Loc.GetString("lathe-menu-reagent-slot-examine"));
}
[PublicAPI]
public bool CanProduce(EntityUid uid, string recipe, int amount = 1, LatheComponent? component = null)
{
return ProtoMan.TryIndex(recipe, out var proto) && CanProduce(uid, proto, amount, component);
}
public bool CanProduce(EntityUid uid, LatheRecipePrototype recipe, int amount = 1, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!HasRecipe(uid, recipe, component))
return false;
if (amount <= 0)
return false;
var materials = _materialStorage.GetStoredMaterials(uid);
return HasMaterials(materials, recipe, component.MaterialUseMultiplier, amount);
}
///
/// Returns whether or not the given lathe can produce some number of a given recipe.
///
///
/// Useful for reducing material lookup with batched checks.
///
/// The lathe that would produce the recipe.
/// The recipe to be produced.
/// The set of materials to check.
/// The number of times the recipe should be made.
public bool CanProduce(Entity ent, LatheRecipePrototype recipe, Dictionary, int> materials, int amount)
{
if (!Resolve(ent, ref ent.Comp))
return false;
if (amount <= 0)
return false;
if (!HasRecipe(ent, recipe, ent.Comp))
return false;
return HasMaterials(materials, recipe, ent.Comp.MaterialUseMultiplier, amount);
}
///
/// Returns whether or not the given materials dictionary can produce copies of .
///
private bool HasMaterials(Dictionary, int> materials, LatheRecipePrototype recipe, float materialMultiplier, int amount = 1)
{
foreach (var (material, needed) in recipe.Materials)
{
if (!materials.TryGetValue(material, out var availableAmount))
return false;
var adjustedAmount = AdjustMaterial(needed, recipe.ApplyMaterialDiscount, materialMultiplier);
if (availableAmount < adjustedAmount * amount)
return false;
}
return true;
}
private void OnEmagged(EntityUid uid, EmagLatheRecipesComponent component, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (_emag.CheckFlag(uid, EmagType.Interaction))
return;
args.Handled = true;
}
public static int AdjustMaterial(int original, bool reduce, float multiplier)
=> reduce ? (int)MathF.Ceiling(original * multiplier) : original;
protected abstract bool HasRecipe(EntityUid uid, LatheRecipePrototype recipe, LatheComponent component);
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
{
if (!obj.WasModified())
return;
BuildInverseRecipeDictionary();
}
private void BuildInverseRecipeDictionary()
{
InverseRecipes.Clear();
foreach (var latheRecipe in ProtoMan.EnumeratePrototypes())
{
if (latheRecipe.Result is not { } result)
continue;
InverseRecipes.GetOrNew(result).Add(latheRecipe);
}
}
public bool TryGetRecipesFromEntity(string prototype, [NotNullWhen(true)] out List? recipes)
{
recipes = new();
if (InverseRecipes.TryGetValue(prototype, out var r))
recipes.AddRange(r);
return recipes.Count != 0;
}
public string GetRecipeName(ProtoId proto)
{
return GetRecipeName(ProtoMan.Index(proto));
}
public string GetRecipeName(LatheRecipePrototype proto)
{
if (!string.IsNullOrWhiteSpace(proto.Name))
return Loc.GetString(proto.Name);
if (proto.Result is { } result)
{
return ProtoMan.Index(result).Name;
}
if (proto.ResultReagents is { } resultReagents)
{
return ContentLocalizationManager.FormatList(resultReagents
.Select(p => Loc.GetString("lathe-menu-result-reagent-display", ("reagent", ProtoMan.Index(p.Key).LocalizedName), ("amount", p.Value)))
.ToList());
}
return string.Empty;
}
[PublicAPI]
public string GetRecipeDescription(ProtoId proto)
{
return GetRecipeDescription(ProtoMan.Index(proto));
}
public string GetRecipeDescription(LatheRecipePrototype proto)
{
if (!string.IsNullOrWhiteSpace(proto.Description))
return Loc.GetString(proto.Description);
if (proto.Result is { } result)
{
return ProtoMan.Index(result).Description;
}
if (proto.ResultReagents is { } resultReagents)
{
// We only use the first one for the description since these descriptions don't combine very well.
var reagent = resultReagents.First().Key;
return ProtoMan.Index(reagent).LocalizedDescription;
}
return string.Empty;
}
}