1
0

FoodSequenceSystem.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using System.Numerics;
  2. using System.Text;
  3. using Content.Server.Nutrition.Components;
  4. using Content.Shared.Chemistry.EntitySystems;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Mobs.Systems;
  7. using Content.Shared.Nutrition;
  8. using Content.Shared.Nutrition.Components;
  9. using Content.Shared.Nutrition.EntitySystems;
  10. using Content.Shared.Nutrition.Prototypes;
  11. using Content.Shared.Popups;
  12. using Content.Shared.Tag;
  13. using Robust.Server.GameObjects;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Random;
  16. namespace Content.Server.Nutrition.EntitySystems;
  17. public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
  18. {
  19. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
  20. [Dependency] private readonly SharedPopupSystem _popup = default!;
  21. [Dependency] private readonly MetaDataSystem _metaData = default!;
  22. [Dependency] private readonly MobStateSystem _mobState = default!;
  23. [Dependency] private readonly TagSystem _tag = default!;
  24. [Dependency] private readonly IRobustRandom _random = default!;
  25. [Dependency] private readonly IPrototypeManager _proto = default!;
  26. [Dependency] private readonly TransformSystem _transform = default!;
  27. public override void Initialize()
  28. {
  29. base.Initialize();
  30. SubscribeLocalEvent<FoodSequenceStartPointComponent, InteractUsingEvent>(OnInteractUsing);
  31. SubscribeLocalEvent<FoodMetamorphableByAddingComponent, FoodSequenceIngredientAddedEvent>(OnIngredientAdded);
  32. }
  33. private void OnInteractUsing(Entity<FoodSequenceStartPointComponent> ent, ref InteractUsingEvent args)
  34. {
  35. if (TryComp<FoodSequenceElementComponent>(args.Used, out var sequenceElement))
  36. args.Handled = TryAddFoodElement(ent, (args.Used, sequenceElement), args.User);
  37. }
  38. private void OnIngredientAdded(Entity<FoodMetamorphableByAddingComponent> ent, ref FoodSequenceIngredientAddedEvent args)
  39. {
  40. if (!TryComp<FoodSequenceStartPointComponent>(args.Start, out var start))
  41. return;
  42. if (!_proto.TryIndex(args.Proto, out var elementProto))
  43. return;
  44. if (!ent.Comp.OnlyFinal || elementProto.Final || start.FoodLayers.Count == start.MaxLayers)
  45. {
  46. TryMetamorph((ent, start));
  47. }
  48. }
  49. private bool TryMetamorph(Entity<FoodSequenceStartPointComponent> start)
  50. {
  51. List<MetamorphRecipePrototype> availableRecipes = new();
  52. foreach (var recipe in _proto.EnumeratePrototypes<MetamorphRecipePrototype>())
  53. {
  54. if (recipe.Key != start.Comp.Key)
  55. continue;
  56. bool allowed = true;
  57. foreach (var rule in recipe.Rules)
  58. {
  59. if (!rule.Check(_proto, EntityManager, start, start.Comp.FoodLayers))
  60. {
  61. allowed = false;
  62. break;
  63. }
  64. }
  65. if (allowed)
  66. availableRecipes.Add(recipe);
  67. }
  68. if (availableRecipes.Count <= 0)
  69. return true;
  70. Metamorf(start, _random.Pick(availableRecipes)); //In general, if there's more than one recipe, the yml-guys screwed up. Maybe some kind of unit test is needed.
  71. QueueDel(start);
  72. return true;
  73. }
  74. private void Metamorf(Entity<FoodSequenceStartPointComponent> start, MetamorphRecipePrototype recipe)
  75. {
  76. var result = SpawnAtPosition(recipe.Result, Transform(start).Coordinates);
  77. //Try putting in container
  78. _transform.DropNextTo(result, (start, Transform(start)));
  79. if (!_solutionContainer.TryGetSolution(result, start.Comp.Solution, out var resultSoln, out var resultSolution))
  80. return;
  81. if (!_solutionContainer.TryGetSolution(start.Owner, start.Comp.Solution, out var startSoln, out var startSolution))
  82. return;
  83. _solutionContainer.RemoveAllSolution(resultSoln.Value); //Remove all YML reagents
  84. resultSoln.Value.Comp.Solution.MaxVolume = startSoln.Value.Comp.Solution.MaxVolume;
  85. _solutionContainer.TryAddSolution(resultSoln.Value, startSolution);
  86. MergeFlavorProfiles(start, result);
  87. MergeTrash(start, result);
  88. MergeTags(start, result);
  89. }
  90. private bool TryAddFoodElement(Entity<FoodSequenceStartPointComponent> start, Entity<FoodSequenceElementComponent> element, EntityUid? user = null)
  91. {
  92. // we can't add a live mouse to a burger.
  93. if (!TryComp<FoodComponent>(element, out var elementFood))
  94. return false;
  95. if (elementFood.RequireDead && _mobState.IsAlive(element))
  96. return false;
  97. //looking for a suitable FoodSequence prototype
  98. if (!element.Comp.Entries.TryGetValue(start.Comp.Key, out var elementProto))
  99. return false;
  100. if (!_proto.TryIndex(elementProto, out var elementIndexed))
  101. return false;
  102. //if we run out of space, we can still put in one last, final finishing element.
  103. if (start.Comp.FoodLayers.Count >= start.Comp.MaxLayers && !elementIndexed.Final || start.Comp.Finished)
  104. {
  105. if (user is not null)
  106. _popup.PopupEntity(Loc.GetString("food-sequence-no-space"), start, user.Value);
  107. return false;
  108. }
  109. //Generate new visual layer
  110. var flip = start.Comp.AllowHorizontalFlip && _random.Prob(0.5f);
  111. var layer = new FoodSequenceVisualLayer(elementIndexed,
  112. _random.Pick(elementIndexed.Sprites),
  113. new Vector2(flip ? -elementIndexed.Scale.X : elementIndexed.Scale.X, elementIndexed.Scale.Y),
  114. new Vector2(
  115. _random.NextFloat(start.Comp.MinLayerOffset.X, start.Comp.MaxLayerOffset.X),
  116. _random.NextFloat(start.Comp.MinLayerOffset.Y, start.Comp.MaxLayerOffset.Y))
  117. );
  118. start.Comp.FoodLayers.Add(layer);
  119. Dirty(start);
  120. if (elementIndexed.Final)
  121. start.Comp.Finished = true;
  122. UpdateFoodName(start);
  123. MergeFoodSolutions(start, element);
  124. MergeFlavorProfiles(start, element);
  125. MergeTrash(start, element);
  126. MergeTags(start, element);
  127. var ev = new FoodSequenceIngredientAddedEvent(start, element, elementProto, user);
  128. RaiseLocalEvent(start, ev);
  129. QueueDel(element);
  130. return true;
  131. }
  132. private void UpdateFoodName(Entity<FoodSequenceStartPointComponent> start)
  133. {
  134. if (start.Comp.NameGeneration is null)
  135. return;
  136. var content = new StringBuilder();
  137. var separator = "";
  138. if (start.Comp.ContentSeparator is not null)
  139. separator = Loc.GetString(start.Comp.ContentSeparator);
  140. HashSet<ProtoId<FoodSequenceElementPrototype>> existedContentNames = new();
  141. foreach (var layer in start.Comp.FoodLayers)
  142. {
  143. if (!existedContentNames.Contains(layer.Proto))
  144. existedContentNames.Add(layer.Proto);
  145. }
  146. var nameCounter = 1;
  147. foreach (var proto in existedContentNames)
  148. {
  149. if (!_proto.TryIndex(proto, out var protoIndexed))
  150. continue;
  151. if (protoIndexed.Name is null)
  152. continue;
  153. content.Append(Loc.GetString(protoIndexed.Name.Value));
  154. if (nameCounter < existedContentNames.Count)
  155. content.Append(separator);
  156. nameCounter++;
  157. }
  158. var newName = Loc.GetString(start.Comp.NameGeneration.Value,
  159. ("prefix", start.Comp.NamePrefix is not null ? Loc.GetString(start.Comp.NamePrefix) : ""),
  160. ("content", content),
  161. ("suffix", start.Comp.NameSuffix is not null ? Loc.GetString(start.Comp.NameSuffix) : ""));
  162. _metaData.SetEntityName(start, newName);
  163. }
  164. private void MergeFoodSolutions(EntityUid start, EntityUid element)
  165. {
  166. if (!TryComp<FoodComponent>(start, out var startFood))
  167. return;
  168. if (!TryComp<FoodComponent>(element, out var elementFood))
  169. return;
  170. if (!_solutionContainer.TryGetSolution(start, startFood.Solution, out var startSolutionEntity, out var startSolution))
  171. return;
  172. if (!_solutionContainer.TryGetSolution(element, elementFood.Solution, out _, out var elementSolution))
  173. return;
  174. startSolution.MaxVolume += elementSolution.MaxVolume;
  175. _solutionContainer.TryAddSolution(startSolutionEntity.Value, elementSolution);
  176. }
  177. private void MergeFlavorProfiles(EntityUid start, EntityUid element)
  178. {
  179. if (!TryComp<FlavorProfileComponent>(start, out var startProfile))
  180. return;
  181. if (!TryComp<FlavorProfileComponent>(element, out var elementProfile))
  182. return;
  183. foreach (var flavor in elementProfile.Flavors)
  184. {
  185. if (startProfile != null && !startProfile.Flavors.Contains(flavor))
  186. startProfile.Flavors.Add(flavor);
  187. }
  188. }
  189. private void MergeTrash(EntityUid start, EntityUid element)
  190. {
  191. if (!TryComp<FoodComponent>(start, out var startFood))
  192. return;
  193. if (!TryComp<FoodComponent>(element, out var elementFood))
  194. return;
  195. foreach (var trash in elementFood.Trash)
  196. {
  197. startFood.Trash.Add(trash);
  198. }
  199. }
  200. private void MergeTags(EntityUid start, EntityUid element)
  201. {
  202. if (!TryComp<TagComponent>(element, out var elementTags))
  203. return;
  204. EnsureComp<TagComponent>(start);
  205. _tag.TryAddTags(start, elementTags.Tags);
  206. }
  207. }