ReagentGrinderSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. using Content.Server.Kitchen.Components;
  2. using Content.Server.Power.Components;
  3. using Content.Server.Power.EntitySystems;
  4. using Content.Server.Stack;
  5. using Content.Shared.Chemistry.EntitySystems;
  6. using Content.Shared.Chemistry.Components;
  7. using Content.Shared.Containers.ItemSlots;
  8. using Content.Shared.Destructible;
  9. using Content.Shared.FixedPoint;
  10. using Content.Shared.Interaction;
  11. using Content.Shared.Kitchen;
  12. using Content.Shared.Kitchen.Components;
  13. using Content.Shared.Popups;
  14. using Content.Shared.Random;
  15. using Content.Shared.Stacks;
  16. using JetBrains.Annotations;
  17. using Robust.Server.GameObjects;
  18. using Robust.Shared.Audio;
  19. using Robust.Shared.Audio.Systems;
  20. using Robust.Shared.Containers;
  21. using Robust.Shared.Timing;
  22. using System.Linq;
  23. using Content.Server.Jittering;
  24. using Content.Shared.Jittering;
  25. using Content.Shared.Power;
  26. namespace Content.Server.Kitchen.EntitySystems
  27. {
  28. [UsedImplicitly]
  29. internal sealed class ReagentGrinderSystem : EntitySystem
  30. {
  31. [Dependency] private readonly IGameTiming _timing = default!;
  32. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainersSystem = default!;
  33. [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
  34. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  35. [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
  36. [Dependency] private readonly StackSystem _stackSystem = default!;
  37. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  38. [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
  39. [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
  40. [Dependency] private readonly RandomHelperSystem _randomHelper = default!;
  41. [Dependency] private readonly JitteringSystem _jitter = default!;
  42. public override void Initialize()
  43. {
  44. base.Initialize();
  45. SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentStartup>(OnActiveGrinderStart);
  46. SubscribeLocalEvent<ActiveReagentGrinderComponent, ComponentRemove>(OnActiveGrinderRemove);
  47. SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>((uid, _, _) => UpdateUiState(uid));
  48. SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUiState(uid));
  49. SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
  50. SubscribeLocalEvent<ReagentGrinderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
  51. SubscribeLocalEvent<ReagentGrinderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
  52. SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemoveAttempt);
  53. SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderToggleAutoModeMessage>(OnToggleAutoModeMessage);
  54. SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderStartMessage>(OnStartMessage);
  55. SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberAllMessage>(OnEjectChamberAllMessage);
  56. SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
  57. }
  58. private void OnToggleAutoModeMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderToggleAutoModeMessage message)
  59. {
  60. entity.Comp.AutoMode = (GrinderAutoMode) (((byte) entity.Comp.AutoMode + 1) % Enum.GetValues(typeof(GrinderAutoMode)).Length);
  61. UpdateUiState(entity);
  62. }
  63. public override void Update(float frameTime)
  64. {
  65. base.Update(frameTime);
  66. var query = EntityQueryEnumerator<ActiveReagentGrinderComponent, ReagentGrinderComponent>();
  67. while (query.MoveNext(out var uid, out var active, out var reagentGrinder))
  68. {
  69. if (active.EndTime > _timing.CurTime)
  70. continue;
  71. reagentGrinder.AudioStream = _audioSystem.Stop(reagentGrinder.AudioStream);
  72. RemCompDeferred<ActiveReagentGrinderComponent>(uid);
  73. var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
  74. var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
  75. if (outputContainer is null || !_solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSoln, out var containerSolution))
  76. continue;
  77. foreach (var item in inputContainer.ContainedEntities.ToList())
  78. {
  79. var solution = active.Program switch
  80. {
  81. GrinderProgram.Grind => GetGrindSolution(item),
  82. GrinderProgram.Juice => CompOrNull<ExtractableComponent>(item)?.JuiceSolution,
  83. _ => null,
  84. };
  85. if (solution is null)
  86. continue;
  87. if (TryComp<StackComponent>(item, out var stack))
  88. {
  89. var totalVolume = solution.Volume * stack.Count;
  90. if (totalVolume <= 0)
  91. continue;
  92. // Maximum number of items we can process in the stack without going over AvailableVolume
  93. // We add a small tolerance, because floats are inaccurate.
  94. var fitsCount = (int) (stack.Count * FixedPoint2.Min(containerSolution.AvailableVolume / totalVolume + 0.01, 1));
  95. if (fitsCount <= 0)
  96. continue;
  97. // Make a copy of the solution to scale
  98. // Otherwise we'll actually change the volume of the remaining stack too
  99. var scaledSolution = new Solution(solution);
  100. scaledSolution.ScaleSolution(fitsCount);
  101. solution = scaledSolution;
  102. _stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel
  103. }
  104. else
  105. {
  106. if (solution.Volume > containerSolution.AvailableVolume)
  107. continue;
  108. var dev = new DestructionEventArgs();
  109. RaiseLocalEvent(item, dev);
  110. QueueDel(item);
  111. }
  112. _solutionContainersSystem.TryAddSolution(containerSoln.Value, solution);
  113. }
  114. _userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
  115. new ReagentGrinderWorkCompleteMessage());
  116. UpdateUiState(uid);
  117. }
  118. }
  119. private void OnActiveGrinderStart(Entity<ActiveReagentGrinderComponent> ent, ref ComponentStartup args)
  120. {
  121. _jitter.AddJitter(ent, -10, 100);
  122. }
  123. private void OnActiveGrinderRemove(Entity<ActiveReagentGrinderComponent> ent, ref ComponentRemove args)
  124. {
  125. RemComp<JitteringComponent>(ent);
  126. }
  127. private void OnEntRemoveAttempt(Entity<ReagentGrinderComponent> entity, ref ContainerIsRemovingAttemptEvent args)
  128. {
  129. if (HasComp<ActiveReagentGrinderComponent>(entity))
  130. args.Cancel();
  131. }
  132. private void OnContainerModified(EntityUid uid, ReagentGrinderComponent reagentGrinder, ContainerModifiedMessage args)
  133. {
  134. UpdateUiState(uid);
  135. var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
  136. _appearanceSystem.SetData(uid, ReagentGrinderVisualState.BeakerAttached, outputContainer.HasValue);
  137. if (reagentGrinder.AutoMode != GrinderAutoMode.Off && !HasComp<ActiveReagentGrinderComponent>(uid) && this.IsPowered(uid, EntityManager))
  138. {
  139. var program = reagentGrinder.AutoMode == GrinderAutoMode.Grind ? GrinderProgram.Grind : GrinderProgram.Juice;
  140. DoWork(uid, reagentGrinder, program);
  141. }
  142. }
  143. private void OnInteractUsing(Entity<ReagentGrinderComponent> entity, ref InteractUsingEvent args)
  144. {
  145. var heldEnt = args.Used;
  146. var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
  147. if (!HasComp<ExtractableComponent>(heldEnt))
  148. {
  149. if (!HasComp<FitsInDispenserComponent>(heldEnt))
  150. {
  151. // This is ugly but we can't use whitelistFailPopup because there are 2 containers with different whitelists.
  152. _popupSystem.PopupEntity(Loc.GetString("reagent-grinder-component-cannot-put-entity-message"), entity.Owner, args.User);
  153. }
  154. // Entity did NOT pass the whitelist for grind/juice.
  155. // Wouldn't want the clown grinding up the Captain's ID card now would you?
  156. // Why am I asking you? You're biased.
  157. return;
  158. }
  159. if (args.Handled)
  160. return;
  161. // Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once.
  162. // Maybe I should have done that for the microwave too?
  163. if (inputContainer.ContainedEntities.Count >= entity.Comp.StorageMaxEntities)
  164. return;
  165. if (!_containerSystem.Insert(heldEnt, inputContainer))
  166. return;
  167. args.Handled = true;
  168. }
  169. private void UpdateUiState(EntityUid uid)
  170. {
  171. ReagentGrinderComponent? grinderComp = null;
  172. if (!Resolve(uid, ref grinderComp))
  173. return;
  174. var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
  175. var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
  176. Solution? containerSolution = null;
  177. var isBusy = HasComp<ActiveReagentGrinderComponent>(uid);
  178. var canJuice = false;
  179. var canGrind = false;
  180. if (outputContainer is not null
  181. && _solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out _, out containerSolution)
  182. && inputContainer.ContainedEntities.Count > 0)
  183. {
  184. canGrind = inputContainer.ContainedEntities.All(CanGrind);
  185. canJuice = inputContainer.ContainedEntities.All(CanJuice);
  186. }
  187. var state = new ReagentGrinderInterfaceState(
  188. isBusy,
  189. outputContainer.HasValue,
  190. this.IsPowered(uid, EntityManager),
  191. canJuice,
  192. canGrind,
  193. grinderComp.AutoMode,
  194. GetNetEntityArray(inputContainer.ContainedEntities.ToArray()),
  195. containerSolution?.Contents.ToArray()
  196. );
  197. _userInterfaceSystem.SetUiState(uid, ReagentGrinderUiKey.Key, state);
  198. }
  199. private void OnStartMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderStartMessage message)
  200. {
  201. if (!this.IsPowered(entity.Owner, EntityManager) || HasComp<ActiveReagentGrinderComponent>(entity))
  202. return;
  203. DoWork(entity.Owner, entity.Comp, message.Program);
  204. }
  205. private void OnEjectChamberAllMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberAllMessage message)
  206. {
  207. var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
  208. if (HasComp<ActiveReagentGrinderComponent>(entity) || inputContainer.ContainedEntities.Count <= 0)
  209. return;
  210. ClickSound(entity);
  211. foreach (var toEject in inputContainer.ContainedEntities.ToList())
  212. {
  213. _containerSystem.Remove(toEject, inputContainer);
  214. _randomHelper.RandomOffset(toEject, 0.4f);
  215. }
  216. UpdateUiState(entity);
  217. }
  218. private void OnEjectChamberContentMessage(Entity<ReagentGrinderComponent> entity, ref ReagentGrinderEjectChamberContentMessage message)
  219. {
  220. if (HasComp<ActiveReagentGrinderComponent>(entity))
  221. return;
  222. var inputContainer = _containerSystem.EnsureContainer<Container>(entity.Owner, SharedReagentGrinder.InputContainerId);
  223. var ent = GetEntity(message.EntityId);
  224. if (_containerSystem.Remove(ent, inputContainer))
  225. {
  226. _randomHelper.RandomOffset(ent, 0.4f);
  227. ClickSound(entity);
  228. UpdateUiState(entity);
  229. }
  230. }
  231. /// <summary>
  232. /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
  233. /// </summary>
  234. /// <param name="uid">The grinder itself</param>
  235. /// <param name="reagentGrinder"></param>
  236. /// <param name="program">Which program, such as grind or juice</param>
  237. private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, GrinderProgram program)
  238. {
  239. var inputContainer = _containerSystem.EnsureContainer<Container>(uid, SharedReagentGrinder.InputContainerId);
  240. var outputContainer = _itemSlotsSystem.GetItemOrNull(uid, SharedReagentGrinder.BeakerSlotId);
  241. // Do we have anything to grind/juice and a container to put the reagents in?
  242. if (inputContainer.ContainedEntities.Count <= 0 || !HasComp<FitsInDispenserComponent>(outputContainer))
  243. return;
  244. SoundSpecifier? sound;
  245. switch (program)
  246. {
  247. case GrinderProgram.Grind when inputContainer.ContainedEntities.All(CanGrind):
  248. sound = reagentGrinder.GrindSound;
  249. break;
  250. case GrinderProgram.Juice when inputContainer.ContainedEntities.All(CanJuice):
  251. sound = reagentGrinder.JuiceSound;
  252. break;
  253. default:
  254. return;
  255. }
  256. var active = AddComp<ActiveReagentGrinderComponent>(uid);
  257. active.EndTime = _timing.CurTime + reagentGrinder.WorkTime * reagentGrinder.WorkTimeMultiplier;
  258. active.Program = program;
  259. reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid,
  260. AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched
  261. _userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
  262. new ReagentGrinderWorkStartedMessage(program));
  263. }
  264. private void ClickSound(Entity<ReagentGrinderComponent> reagentGrinder)
  265. {
  266. _audioSystem.PlayPvs(reagentGrinder.Comp.ClickSound, reagentGrinder.Owner, AudioParams.Default.WithVolume(-2f));
  267. }
  268. private Solution? GetGrindSolution(EntityUid uid)
  269. {
  270. if (TryComp<ExtractableComponent>(uid, out var extractable)
  271. && extractable.GrindableSolution is not null
  272. && _solutionContainersSystem.TryGetSolution(uid, extractable.GrindableSolution, out _, out var solution))
  273. {
  274. return solution;
  275. }
  276. else
  277. return null;
  278. }
  279. private bool CanGrind(EntityUid uid)
  280. {
  281. var solutionName = CompOrNull<ExtractableComponent>(uid)?.GrindableSolution;
  282. return solutionName is not null && _solutionContainersSystem.TryGetSolution(uid, solutionName, out _, out _);
  283. }
  284. private bool CanJuice(EntityUid uid)
  285. {
  286. return CompOrNull<ExtractableComponent>(uid)?.JuiceSolution is not null;
  287. }
  288. }
  289. }