mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
* LatheMenu: cache where possible in PopulateRecipes * Princess Cheeseballs' review + protected HasRecipe Co-authored-by: Princess-Cheeseballs <66055347+Princess-Cheeseballs@users.noreply@github.com> * private HasMaterials with comment --------- Co-authored-by: Princess-Cheeseballs <66055347+Princess-Cheeseballs@users.noreply@github.com>
232 lines
7.7 KiB
C#
232 lines
7.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// This handles...
|
|
/// </summary>
|
|
public abstract partial class SharedLatheSystem : EntitySystem
|
|
{
|
|
[Dependency] private SharedMaterialStorageSystem _materialStorage = default!;
|
|
[Dependency] private EmagSystem _emag = default!;
|
|
|
|
public readonly Dictionary<string, List<LatheRecipePrototype>> InverseRecipes = new();
|
|
public const int MaxItemsPerRequest = 10_000;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<EmagLatheRecipesComponent, GotEmaggedEvent>(OnEmagged);
|
|
SubscribeLocalEvent<LatheComponent, ExaminedEvent>(OnExamined);
|
|
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
|
|
|
BuildInverseRecipeDictionary();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the set of all recipes that a lathe could possibly ever create (e.g., if all techs were unlocked).
|
|
/// </summary>
|
|
public HashSet<ProtoId<LatheRecipePrototype>> GetAllPossibleRecipes(LatheComponent component)
|
|
{
|
|
var recipes = new HashSet<ProtoId<LatheRecipePrototype>>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add every recipe in the list of recipe packs to a single hashset.
|
|
/// </summary>
|
|
public void AddRecipesFromPacks(HashSet<ProtoId<LatheRecipePrototype>> recipes, IEnumerable<ProtoId<LatheRecipePackPrototype>> packs)
|
|
{
|
|
foreach (var id in packs)
|
|
{
|
|
var pack = ProtoMan.Index(id);
|
|
recipes.UnionWith(pack.Recipes);
|
|
}
|
|
}
|
|
|
|
private void OnExamined(Entity<LatheComponent> 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<LatheRecipePrototype>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether or not the given lathe can produce some number of a given recipe.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Useful for reducing material lookup with batched checks.
|
|
/// </remarks>
|
|
/// <param name="ent">The lathe that would produce the recipe.</param>
|
|
/// <param name="recipe">The recipe to be produced.</param>
|
|
/// <param name="materials">The set of materials to check.</param>
|
|
/// <param name="amount">The number of times the recipe should be made.</param>
|
|
public bool CanProduce(Entity<LatheComponent?> ent, LatheRecipePrototype recipe, Dictionary<ProtoId<MaterialPrototype>, 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether or not the given materials dictionary can produce <paramref name="amount"/> copies of <paramref name="recipe"/>.
|
|
/// </summary>
|
|
private bool HasMaterials(Dictionary<ProtoId<MaterialPrototype>, 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<LatheRecipePrototype>())
|
|
return;
|
|
BuildInverseRecipeDictionary();
|
|
}
|
|
|
|
private void BuildInverseRecipeDictionary()
|
|
{
|
|
InverseRecipes.Clear();
|
|
foreach (var latheRecipe in ProtoMan.EnumeratePrototypes<LatheRecipePrototype>())
|
|
{
|
|
if (latheRecipe.Result is not { } result)
|
|
continue;
|
|
|
|
InverseRecipes.GetOrNew(result).Add(latheRecipe);
|
|
}
|
|
}
|
|
|
|
public bool TryGetRecipesFromEntity(string prototype, [NotNullWhen(true)] out List<LatheRecipePrototype>? recipes)
|
|
{
|
|
recipes = new();
|
|
if (InverseRecipes.TryGetValue(prototype, out var r))
|
|
recipes.AddRange(r);
|
|
return recipes.Count != 0;
|
|
}
|
|
|
|
public string GetRecipeName(ProtoId<LatheRecipePrototype> 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<LatheRecipePrototype> 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;
|
|
}
|
|
}
|