Knowledge system (#770)

* remove all requirements

* clean up and renaming to knowledge

* update code

* add admin knowledge verbs

* move shoes under pants

* knowledge based recipes

* clean up

* sewing knowledges

* knowledge dependencies

* knowledge learning objects

* more knowledge

* metallforging skill

* knowledge books

* start knowledges, T1 and T2 books in demiplanes

* remove coins from demiplanes

* roundstart knowledge
This commit is contained in:
Ed
2025-01-17 00:08:13 +03:00
committed by GitHub
parent 5af56d4655
commit 0783eb1380
59 changed files with 1002 additions and 685 deletions

View File

@@ -1,7 +1,7 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared._CP14.Farming;
using Content.Shared._CP14.Skills;
using Content.Shared._CP14.Knowledge;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
@@ -35,8 +35,6 @@ public sealed class InjectorSystem : SharedInjectorSystem
private bool TryUseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user)
{
RaiseLocalEvent(injector, new CP14TrySkillIssueEvent(user)); //CP14 Skill issue event
var isOpenOrIgnored = injector.Comp.IgnoreClosed || !_openable.IsClosed(target);
// Handle injecting/drawing for solutions
if (injector.Comp.ToggleState == InjectorToggleMode.Inject)

View File

@@ -0,0 +1,27 @@
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Knowledge;
/// <summary>
/// a component that can be hung on an entity to immediately teach it some skills
/// </summary>
[UsedImplicitly]
public sealed partial class CP14AddKnowledgeSpecial : JobSpecial
{
[DataField(required: true), ViewVariables(VVAccess.ReadOnly)]
public List<ProtoId<CP14KnowledgePrototype>> Knowledge = new();
public override void AfterEquip(EntityUid mob)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var knowledgeSystem = entMan.System<CP14KnowledgeSystem>();
foreach (var knowledge in Knowledge)
{
knowledgeSystem.TryLearnKnowledge(mob, knowledge, true, true);
}
}
}

View File

@@ -0,0 +1,254 @@
using System.Text;
using Content.Server.Chat.Managers;
using Content.Server.Mind;
using Content.Server.Popups;
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Knowledge.Components;
using Content.Shared._CP14.Knowledge.Prototypes;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Knowledge;
public sealed partial class CP14KnowledgeSystem : SharedCP14KnowledgeSystem
{
[Dependency] private readonly ISharedAdminManager _admin = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14AutoAddKnowledgeComponent, MapInitEvent>(AutoAddSkill);
SubscribeLocalEvent<CP14KnowledgeStorageComponent, GetVerbsEvent<Verb>>(AddKnowledgeAdminVerb);
SubscribeLocalEvent<CP14KnowledgeLearningSourceComponent, GetVerbsEvent<Verb>>(AddKnowledgeLearningVerb);
SubscribeLocalEvent<CP14KnowledgeStorageComponent, CP14KnowledgeLearnDoAfterEvent>(KnowledgeLearnedEvent);
}
private void KnowledgeLearnedEvent(Entity<CP14KnowledgeStorageComponent> ent, ref CP14KnowledgeLearnDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;
args.Handled = true;
TryLearnKnowledge(ent, args.Knowledge);
}
private void AddKnowledgeLearningVerb(Entity<CP14KnowledgeLearningSourceComponent> ent, ref GetVerbsEvent<Verb> args)
{
var user = args.User;
foreach (var knowledge in ent.Comp.Knowledges)
{
if (!_proto.TryIndex(knowledge, out var indexedKnowledge))
continue;
args.Verbs.Add(new Verb()
{
Text = $"{Loc.GetString(indexedKnowledge.Name)}",
Message = Loc.GetString(indexedKnowledge.Desc),
Category = VerbCategory.CP14KnowledgeLearn,
Act = () =>
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
user,
ent.Comp.DoAfter,
new CP14KnowledgeLearnDoAfterEvent() {Knowledge = knowledge},
user,
ent,
ent)
{
BreakOnDamage = true,
BreakOnMove = true,
});
},
Disabled = HasKnowledge(user, knowledge),
Impact = LogImpact.Medium,
});
}
}
private void AddKnowledgeAdminVerb(Entity<CP14KnowledgeStorageComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin))
return;
//Remove knowledge
foreach (var knowledge in ent.Comp.Knowledges)
{
if (!_proto.TryIndex(knowledge, out var indexedKnowledge))
continue;
args.Verbs.Add(new Verb()
{
Text = $"{Loc.GetString(indexedKnowledge.Name)}",
Message = Loc.GetString(indexedKnowledge.Desc),
Category = VerbCategory.CP14KnowledgeRemove,
Act = () =>
{
TryForgotKnowledge(ent, knowledge);
},
Impact = LogImpact.High,
});
}
//Add knowledge
foreach (var knowledge in _proto.EnumeratePrototypes<CP14KnowledgePrototype>())
{
if (ent.Comp.Knowledges.Contains(knowledge))
continue;
args.Verbs.Add(new Verb()
{
Text = $"{Loc.GetString(knowledge.Name)}",
Message = Loc.GetString(knowledge.Desc),
Category = VerbCategory.CP14KnowledgeAdd,
Act = () =>
{
TryLearnKnowledge(ent, knowledge, false);
},
Impact = LogImpact.High,
});
}
}
private void AutoAddSkill(Entity<CP14AutoAddKnowledgeComponent> ent, ref MapInitEvent args)
{
foreach (var knowledge in ent.Comp.Knowledge)
{
TryLearnKnowledge(ent, knowledge);
}
RemComp(ent, ent.Comp);
}
public bool TryLearnKnowledge(EntityUid uid, ProtoId<CP14KnowledgePrototype> proto, bool force = false, bool silent = false)
{
if (!TryComp<CP14KnowledgeStorageComponent>(uid, out var knowledgeStorage))
return false;
if (!_proto.TryIndex(proto, out var indexedKnowledge))
return false;
if (knowledgeStorage.Knowledges.Contains(proto))
return false;
foreach (var dependency in indexedKnowledge.Dependencies)
{
if (!_proto.TryIndex(dependency, out var indexedDependency))
return false;
if (force)
{
//If we teach by force - we automatically teach all the basics that are necessary for that skill.
if (!TryLearnKnowledge(uid, dependency, true))
return false;
}
else
{
var passed = true;
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-cant-learn-knowledge-dependencies",
("target", Loc.GetString(indexedKnowledge.Desc))));
//We cant learnt
if (!HasKnowledge(uid, dependency))
{
passed = false;
sb.Append("\n- " + Loc.GetString(indexedDependency.Desc));
}
if (!passed)
{
if (!silent && _mind.TryGetMind(uid, out var mind, out var mindComp) && mindComp.Session is not null)
{
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", sb.ToString()));
_chat.ChatMessageToOne(
ChatChannel.Server,
sb.ToString(),
wrappedMessage,
default,
false,
mindComp.Session.Channel);
}
return false;
}
}
}
//TODO: coding on a sleepy head is a bad idea. Remove duplicate variables. >:(
if (!silent && _mind.TryGetMind(uid, out var mind2, out var mindComp2) && mindComp2.Session is not null)
{
var message = Loc.GetString("cp14-learned-new-knowledge", ("name", Loc.GetString(indexedKnowledge.Name)));
var wrappedMessage2 = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(
ChatChannel.Server,
message,
wrappedMessage2,
default,
false,
mindComp2.Session.Channel);
}
_adminLogger.Add(
LogType.Mind,
LogImpact.Medium,
$"{EntityManager.ToPrettyString(uid):player} learned new knowledge: {Loc.GetString(indexedKnowledge.Name)}");
return knowledgeStorage.Knowledges.Add(proto);
}
public bool TryForgotKnowledge(EntityUid uid, ProtoId<CP14KnowledgePrototype> proto, bool silent = false)
{
if (!TryComp<CP14KnowledgeStorageComponent>(uid, out var knowledgeStorage))
return false;
if (!knowledgeStorage.Knowledges.Contains(proto))
return false;
if (!_proto.TryIndex(proto, out var indexedKnowledge))
return false;
knowledgeStorage.Knowledges.Remove(proto);
if (_mind.TryGetMind(uid, out var mind, out var mindComp) && mindComp.Session is not null)
{
if (!silent)
{
var message = Loc.GetString("cp14-forgot-knowledge", ("name", Loc.GetString(indexedKnowledge.Name)));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(
ChatChannel.Server,
message,
wrappedMessage,
default,
false,
mindComp.Session.Channel);
}
}
_adminLogger.Add(
LogType.Mind,
LogImpact.Medium,
$"{EntityManager.ToPrettyString(uid):player} forgot knowledge: {Loc.GetString(indexedKnowledge.Name)}");
return true;
}
}

View File

@@ -1,166 +0,0 @@
using Content.Server._CP14.Alchemy;
using Content.Server.Popups;
using Content.Shared._CP14.MeleeWeapon.EntitySystems;
using Content.Shared._CP14.Skills;
using Content.Shared._CP14.Skills.Components;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Skills;
public sealed partial class CP14SkillSystem : SharedCP14SkillSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14SkillRequirementComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<CP14SkillRequirementComponent, GunShotEvent>(OnGunShot);
SubscribeLocalEvent<CP14SkillRequirementComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<CP14SkillRequirementComponent, CP14TrySkillIssueEvent>(OnSimpleSkillIssue);
SubscribeLocalEvent<CP14SkillRequirementComponent, SharpingEvent>(OnSharpning);
SubscribeLocalEvent<CP14SkillRequirementComponent, PestleGrindingEvent>(OnPestleGrinding);
}
private void OnExamined(Entity<CP14SkillRequirementComponent> requirement, ref ExaminedEvent args)
{
var text = string.Empty;
text += "\n" + Loc.GetString("cp14-skill-label") + "\n";
var canUse = HasEnoughSkillToUse(args.Examiner, requirement, out var missingSkills);
text += Loc.GetString(requirement.Comp.NeedAll ? "cp14-skill-examined-need-all" : "cp14-skill-examined", ("item", MetaData(requirement).EntityName)) + "\n";
foreach (var skill in requirement.Comp.RequiredSkills)
{
var name = _proto.Index(skill).Name;
if (name == null)
continue;
var color = missingSkills.Contains(skill) ? "#c23030" : "#3fc488";
text += Loc.GetString("cp14-skill-examined-skill", ("color", color), ("skill", Loc.GetString(name))) + "\n";
}
args.PushMarkup(text);
}
private void OnPestleGrinding(Entity<CP14SkillRequirementComponent> requirement, ref PestleGrindingEvent args)
{
if (!_random.Prob(requirement.Comp.FuckupChance))
return;
if (HasEnoughSkillToUse(args.User, requirement, out _))
return;
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-push", ("item", MetaData(args.Target).EntityName)),
args.User,
args.User,
PopupType.Large);
_hands.TryDrop(args.User, args.Target);
_throwing.TryThrow(args.Target, _random.NextAngle().ToWorldVec(), 1, args.User);
}
private void OnSharpning(Entity<CP14SkillRequirementComponent> requirement, ref SharpingEvent args)
{
if (!_random.Prob(requirement.Comp.FuckupChance))
return;
if (HasEnoughSkillToUse(args.User, requirement, out _))
return;
switch (_random.Next(2))
{
case 0:
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-sharp-weapon-harm", ("item", MetaData(args.Target).EntityName)), args.User, args.User, PopupType.Large);
var weaponDamage = new DamageSpecifier();
weaponDamage.DamageDict.Add("Blunt", _random.NextFloat(5));
_damageable.TryChangeDamage(args.Target, weaponDamage);
break;
case 1:
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-sharp-self-harm"), args.User, args.User, PopupType.Large);
var damage = new DamageSpecifier();
if (TryComp<MeleeWeaponComponent>(requirement, out var melee))
damage = melee.Damage;
else
damage.DamageDict.Add("Slash", _random.NextFloat(10));
_damageable.TryChangeDamage(args.User, damage);
break;
}
}
private void OnSimpleSkillIssue(Entity<CP14SkillRequirementComponent> requirement, ref CP14TrySkillIssueEvent args)
{
if (!_random.Prob(requirement.Comp.FuckupChance))
return;
if (HasEnoughSkillToUse(args.User, requirement, out _))
return;
BasicSkillIssue(args.User, requirement);
}
private void OnMeleeHit(Entity<CP14SkillRequirementComponent> requirement, ref MeleeHitEvent args)
{
if (!_random.Prob(requirement.Comp.FuckupChance))
return;
if (HasEnoughSkillToUse(args.User, requirement, out _))
return;
switch (_random.Next(2))
{
case 0:
BasicSkillIssue(args.User, requirement);
break;
case 1:
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-self-harm"), args.User, args.User, PopupType.Large);
var damage = new DamageSpecifier();
if (TryComp<MeleeWeaponComponent>(requirement, out var melee))
damage = melee.Damage;
else
damage.DamageDict.Add("Blunt", _random.NextFloat(10));
_damageable.TryChangeDamage(args.User, damage);
break;
}
}
private void OnGunShot(Entity<CP14SkillRequirementComponent> requirement, ref GunShotEvent args)
{
if (!_random.Prob(requirement.Comp.FuckupChance))
return;
if (HasEnoughSkillToUse(args.User, requirement, out _))
return;
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-recoil"), args.User, args.User, PopupType.Large);
_hands.TryDrop(args.User, requirement);
_throwing.TryThrow(requirement, _random.NextAngle().ToWorldVec(), _random.NextFloat(5), args.User);
var damage = new DamageSpecifier();
damage.DamageDict.Add("Blunt", _random.NextFloat(10));
_damageable.TryChangeDamage(args.User, damage);
}
private void BasicSkillIssue(EntityUid user, EntityUid item, float throwPower = 1)
{
_popup.PopupEntity(Loc.GetString("cp14-skill-issue-drops", ("item", MetaData(item).EntityName)), user, user, PopupType.Large);
_hands.TryDrop(user, item);
_throwing.TryThrow(item, _random.NextAngle().ToWorldVec(), throwPower, user);
}
}

View File

@@ -20,7 +20,7 @@ public sealed partial class CP14WorkbenchSystem
StartCraft(entity, args.Actor, prototype);
}
private void UpdateUIRecipes(Entity<CP14WorkbenchComponent> entity)
private void UpdateUIRecipes(Entity<CP14WorkbenchComponent> entity, EntityUid user)
{
var placedEntities = _lookup.GetEntitiesInRange(Transform(entity).Coordinates, entity.Comp.WorkbenchRadius);
@@ -30,7 +30,13 @@ public sealed partial class CP14WorkbenchSystem
if (!_proto.TryIndex(recipeId, out var indexedRecipe))
continue;
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, CanCraftRecipe(indexedRecipe, placedEntities));
if (indexedRecipe.KnowledgeRequired is not null)
{
if (!_knowledge.HasKnowledge(user, indexedRecipe.KnowledgeRequired.Value))
continue;
}
var entry = new CP14WorkbenchUiRecipesEntry(recipeId, CanCraftRecipe(indexedRecipe, placedEntities, user));
recipes.Add(entry);
}

View File

@@ -6,6 +6,7 @@
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Stack;
using Content.Shared._CP14.Knowledge;
using Content.Shared._CP14.Workbench;
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.Chemistry.EntitySystems;
@@ -29,6 +30,7 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedCP14KnowledgeSystem _knowledge = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<StackComponent> _stackQuery;
@@ -64,10 +66,9 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
private void OnBeforeUIOpen(Entity<CP14WorkbenchComponent> ent, ref BeforeActivatableUIOpenEvent args)
{
UpdateUIRecipes(ent);
UpdateUIRecipes(ent, args.User);
}
// TODO: Replace Del to QueueDel when it's will be works with events
private void OnCraftFinished(Entity<CP14WorkbenchComponent> ent, ref CP14CraftDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
@@ -78,23 +79,26 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
var placedEntities = _lookup.GetEntitiesInRange(Transform(ent).Coordinates, ent.Comp.WorkbenchRadius, LookupFlags.Uncontained);
if (!CanCraftRecipe(recipe, placedEntities))
if (!CanCraftRecipe(recipe, placedEntities, args.User))
{
_popup.PopupEntity(Loc.GetString("cp14-workbench-no-resource"), ent, args.User);
_popup.PopupEntity(Loc.GetString("cp14-workbench-cant-craft"), ent, args.User);
return;
}
if (!_proto.TryIndex(args.Recipe, out var indexedRecipe))
return;
if (recipe.KnowledgeRequired is not null)
_knowledge.UseKnowledge(args.User, recipe.KnowledgeRequired.Value);
var resultEntity = Spawn(indexedRecipe.Result);
_stack.TryMergeToContacts(resultEntity);
_solutionContainer.TryGetSolution(resultEntity, recipe.Solution, out var resultSoln, out var resultSolution);
if (recipe.TryMergeSolutions && resultSoln is not null)
_solutionContainer.TryGetSolution(resultEntity, recipe.Solution, out var resultSolution, out _);
if (recipe.TryMergeSolutions && resultSolution is not null)
{
resultSoln.Value.Comp.Solution.MaxVolume = 0;
_solutionContainer.RemoveAllSolution(resultSoln.Value); //If we combine ingredient solutions, we do not use the default solution prescribed in the entity.
resultSolution.Value.Comp.Solution.MaxVolume = 0;
_solutionContainer.RemoveAllSolution(resultSolution.Value); //If we combine ingredient solutions, we do not use the default solution prescribed in the entity.
}
foreach (var requiredIngredient in recipe.Entities)
@@ -102,7 +106,8 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
var requiredCount = requiredIngredient.Value;
foreach (var placedEntity in placedEntities)
{
if (!TryComp<MetaDataComponent>(placedEntity, out var metaData) || metaData.EntityPrototype is null)
var metaData = MetaData(placedEntity);
if (metaData.EntityPrototype is null)
continue;
var placedProto = metaData.EntityPrototype.ID;
@@ -110,11 +115,11 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
{
// Trying merge solutions
if (recipe.TryMergeSolutions
&& resultSoln is not null
&& resultSolution is not null
&& _solutionContainer.TryGetSolution(placedEntity, recipe.Solution, out var ingredientSoln, out var ingredientSolution))
{
resultSoln.Value.Comp.Solution.MaxVolume += ingredientSoln.Value.Comp.Solution.MaxVolume;
_solutionContainer.TryAddSolution(resultSoln.Value, ingredientSolution);
resultSolution.Value.Comp.Solution.MaxVolume += ingredientSoln.Value.Comp.Solution.MaxVolume;
_solutionContainer.TryAddSolution(resultSolution.Value, ingredientSolution);
}
requiredCount--;
@@ -145,7 +150,7 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
}
}
_transform.SetCoordinates(resultEntity, Transform(ent).Coordinates);
UpdateUIRecipes(ent);
UpdateUIRecipes(ent, args.User);
args.Handled = true;
}
@@ -172,8 +177,13 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
_audio.PlayPvs(recipe.OverrideCraftSound ?? workbench.Comp.CraftSound, workbench);
}
private bool CanCraftRecipe(CP14WorkbenchRecipePrototype recipe, HashSet<EntityUid> entities)
private bool CanCraftRecipe(CP14WorkbenchRecipePrototype recipe, HashSet<EntityUid> entities, EntityUid user)
{
//Knowledge check
if (recipe.KnowledgeRequired is not null && !_knowledge.HasKnowledge(user, recipe.KnowledgeRequired.Value))
return false;
//Ingredients check
var indexedIngredients = IndexIngredients(entities);
foreach (var requiredIngredient in recipe.Entities)
{
@@ -202,25 +212,6 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
return true;
}
private string GetCraftRecipeMessage(string desc, CP14WorkbenchRecipePrototype recipe)
{
var result = desc + "\n \n" + Loc.GetString("cp14-workbench-recipe-list")+ "\n";
foreach (var pair in recipe.Entities)
{
var proto = _proto.Index(pair.Key);
result += $"{proto.Name} x{pair.Value}\n";
}
foreach (var pair in recipe.Stacks)
{
var proto = _proto.Index(pair.Key);
result += $"{proto.Name} x{pair.Value}\n";
}
return result;
}
private Dictionary<EntProtoId, int> IndexIngredients(HashSet<EntityUid> ingredients)
{
var indexedIngredients = new Dictionary<EntProtoId, int>();