1
0

MicrowaveSystem.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Body.Systems;
  3. using Content.Server.Construction;
  4. using Content.Server.Explosion.EntitySystems;
  5. using Content.Server.DeviceLinking.Events;
  6. using Content.Server.DeviceLinking.Systems;
  7. using Content.Server.Hands.Systems;
  8. using Content.Server.Kitchen.Components;
  9. using Content.Server.Power.Components;
  10. using Content.Server.Power.EntitySystems;
  11. using Content.Server.Temperature.Components;
  12. using Content.Server.Temperature.Systems;
  13. using Content.Shared.Body.Components;
  14. using Content.Shared.Body.Part;
  15. using Content.Shared.Chemistry.Components.SolutionManager;
  16. using Content.Shared.Chemistry.EntitySystems;
  17. using Content.Shared.Chemistry.Reaction;
  18. using Content.Shared.Construction.EntitySystems;
  19. using Content.Shared.Database;
  20. using Content.Shared.Destructible;
  21. using Content.Shared.FixedPoint;
  22. using Content.Shared.Interaction;
  23. using Content.Shared.Interaction.Events;
  24. using Robust.Shared.Random;
  25. using Robust.Shared.Audio;
  26. using Content.Server.Lightning;
  27. using Content.Shared.Item;
  28. using Content.Shared.Kitchen;
  29. using Content.Shared.Kitchen.Components;
  30. using Content.Shared.Popups;
  31. using Content.Shared.Power;
  32. using Content.Shared.Tag;
  33. using Robust.Server.GameObjects;
  34. using Robust.Shared.Audio.Systems;
  35. using Robust.Shared.Containers;
  36. using Robust.Shared.Player;
  37. using System.Linq;
  38. using Robust.Shared.Prototypes;
  39. using Robust.Shared.Timing;
  40. using Content.Shared.Stacks;
  41. using Content.Server.Construction.Components;
  42. using Content.Shared.Chat;
  43. using Content.Shared.Damage;
  44. using Robust.Shared.Utility;
  45. namespace Content.Server.Kitchen.EntitySystems
  46. {
  47. public sealed class MicrowaveSystem : EntitySystem
  48. {
  49. [Dependency] private readonly BodySystem _bodySystem = default!;
  50. [Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
  51. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  52. [Dependency] private readonly PowerReceiverSystem _power = default!;
  53. [Dependency] private readonly RecipeManager _recipeManager = default!;
  54. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  55. [Dependency] private readonly SharedAudioSystem _audio = default!;
  56. [Dependency] private readonly LightningSystem _lightning = default!;
  57. [Dependency] private readonly IRobustRandom _random = default!;
  58. [Dependency] private readonly IGameTiming _gameTiming = default!;
  59. [Dependency] private readonly ExplosionSystem _explosion = default!;
  60. [Dependency] private readonly SharedContainerSystem _container = default!;
  61. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
  62. [Dependency] private readonly TagSystem _tag = default!;
  63. [Dependency] private readonly TemperatureSystem _temperature = default!;
  64. [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
  65. [Dependency] private readonly HandsSystem _handsSystem = default!;
  66. [Dependency] private readonly SharedItemSystem _item = default!;
  67. [Dependency] private readonly SharedStackSystem _stack = default!;
  68. [Dependency] private readonly IPrototypeManager _prototype = default!;
  69. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  70. [Dependency] private readonly SharedSuicideSystem _suicide = default!;
  71. [ValidatePrototypeId<EntityPrototype>]
  72. private const string MalfunctionSpark = "Spark";
  73. public override void Initialize()
  74. {
  75. base.Initialize();
  76. SubscribeLocalEvent<MicrowaveComponent, ComponentInit>(OnInit);
  77. SubscribeLocalEvent<MicrowaveComponent, MapInitEvent>(OnMapInit);
  78. SubscribeLocalEvent<MicrowaveComponent, SolutionContainerChangedEvent>(OnSolutionChange);
  79. SubscribeLocalEvent<MicrowaveComponent, EntInsertedIntoContainerMessage>(OnContentUpdate);
  80. SubscribeLocalEvent<MicrowaveComponent, EntRemovedFromContainerMessage>(OnContentUpdate);
  81. SubscribeLocalEvent<MicrowaveComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(AnchorableSystem) });
  82. SubscribeLocalEvent<MicrowaveComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
  83. SubscribeLocalEvent<MicrowaveComponent, BreakageEventArgs>(OnBreak);
  84. SubscribeLocalEvent<MicrowaveComponent, PowerChangedEvent>(OnPowerChanged);
  85. SubscribeLocalEvent<MicrowaveComponent, AnchorStateChangedEvent>(OnAnchorChanged);
  86. SubscribeLocalEvent<MicrowaveComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
  87. SubscribeLocalEvent<MicrowaveComponent, SignalReceivedEvent>(OnSignalReceived);
  88. SubscribeLocalEvent<MicrowaveComponent, MicrowaveStartCookMessage>((u, c, m) => Wzhzhzh(u, c, m.Actor));
  89. SubscribeLocalEvent<MicrowaveComponent, MicrowaveEjectMessage>(OnEjectMessage);
  90. SubscribeLocalEvent<MicrowaveComponent, MicrowaveEjectSolidIndexedMessage>(OnEjectIndex);
  91. SubscribeLocalEvent<MicrowaveComponent, MicrowaveSelectCookTimeMessage>(OnSelectTime);
  92. SubscribeLocalEvent<ActiveMicrowaveComponent, ComponentStartup>(OnCookStart);
  93. SubscribeLocalEvent<ActiveMicrowaveComponent, ComponentShutdown>(OnCookStop);
  94. SubscribeLocalEvent<ActiveMicrowaveComponent, EntInsertedIntoContainerMessage>(OnActiveMicrowaveInsert);
  95. SubscribeLocalEvent<ActiveMicrowaveComponent, EntRemovedFromContainerMessage>(OnActiveMicrowaveRemove);
  96. SubscribeLocalEvent<ActivelyMicrowavedComponent, OnConstructionTemperatureEvent>(OnConstructionTemp);
  97. SubscribeLocalEvent<ActivelyMicrowavedComponent, SolutionRelayEvent<ReactionAttemptEvent>>(OnReactionAttempt);
  98. SubscribeLocalEvent<FoodRecipeProviderComponent, GetSecretRecipesEvent>(OnGetSecretRecipes);
  99. }
  100. private void OnCookStart(Entity<ActiveMicrowaveComponent> ent, ref ComponentStartup args)
  101. {
  102. if (!TryComp<MicrowaveComponent>(ent, out var microwaveComponent))
  103. return;
  104. SetAppearance(ent.Owner, MicrowaveVisualState.Cooking, microwaveComponent);
  105. microwaveComponent.PlayingStream =
  106. _audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5))?.Entity;
  107. }
  108. private void OnCookStop(Entity<ActiveMicrowaveComponent> ent, ref ComponentShutdown args)
  109. {
  110. if (!TryComp<MicrowaveComponent>(ent, out var microwaveComponent))
  111. return;
  112. SetAppearance(ent.Owner, MicrowaveVisualState.Idle, microwaveComponent);
  113. microwaveComponent.PlayingStream = _audio.Stop(microwaveComponent.PlayingStream);
  114. }
  115. private void OnActiveMicrowaveInsert(Entity<ActiveMicrowaveComponent> ent, ref EntInsertedIntoContainerMessage args)
  116. {
  117. var microwavedComp = AddComp<ActivelyMicrowavedComponent>(args.Entity);
  118. microwavedComp.Microwave = ent.Owner;
  119. }
  120. private void OnActiveMicrowaveRemove(Entity<ActiveMicrowaveComponent> ent, ref EntRemovedFromContainerMessage args)
  121. {
  122. EntityManager.RemoveComponentDeferred<ActivelyMicrowavedComponent>(args.Entity);
  123. }
  124. // Stop items from transforming through constructiongraphs while being microwaved.
  125. // They might be reserved for a microwave recipe.
  126. private void OnConstructionTemp(Entity<ActivelyMicrowavedComponent> ent, ref OnConstructionTemperatureEvent args)
  127. {
  128. args.Result = HandleResult.False;
  129. }
  130. // Stop reagents from reacting if they are currently reserved for a microwave recipe.
  131. // For example Egg would cook into EggCooked, causing it to not being removed once we are done microwaving.
  132. private void OnReactionAttempt(Entity<ActivelyMicrowavedComponent> ent, ref SolutionRelayEvent<ReactionAttemptEvent> args)
  133. {
  134. if (!TryComp<ActiveMicrowaveComponent>(ent.Comp.Microwave, out var activeMicrowaveComp))
  135. return;
  136. if (activeMicrowaveComp.PortionedRecipe.Item1 == null) // no recipe selected
  137. return;
  138. var recipeReagents = activeMicrowaveComp.PortionedRecipe.Item1.IngredientsReagents.Keys;
  139. foreach (var reagent in recipeReagents)
  140. {
  141. if (args.Event.Reaction.Reactants.ContainsKey(reagent))
  142. {
  143. args.Event.Cancelled = true;
  144. return;
  145. }
  146. }
  147. }
  148. /// <summary>
  149. /// Adds temperature to every item in the microwave,
  150. /// based on the time it took to microwave.
  151. /// </summary>
  152. /// <param name="component">The microwave that is heating up.</param>
  153. /// <param name="time">The time on the microwave, in seconds.</param>
  154. private void AddTemperature(MicrowaveComponent component, float time)
  155. {
  156. var heatToAdd = time * component.BaseHeatMultiplier;
  157. foreach (var entity in component.Storage.ContainedEntities)
  158. {
  159. if (TryComp<TemperatureComponent>(entity, out var tempComp))
  160. _temperature.ChangeHeat(entity, heatToAdd * component.ObjectHeatMultiplier, false, tempComp);
  161. if (!TryComp<SolutionContainerManagerComponent>(entity, out var solutions))
  162. continue;
  163. foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((entity, solutions)))
  164. {
  165. var solution = soln.Comp.Solution;
  166. if (solution.Temperature > component.TemperatureUpperThreshold)
  167. continue;
  168. _solutionContainer.AddThermalEnergy(soln, heatToAdd);
  169. }
  170. }
  171. }
  172. private void SubtractContents(MicrowaveComponent component, FoodRecipePrototype recipe)
  173. {
  174. // TODO Turn recipe.IngredientsReagents into a ReagentQuantity[]
  175. var totalReagentsToRemove = new Dictionary<string, FixedPoint2>(recipe.IngredientsReagents);
  176. // this is spaghetti ngl
  177. foreach (var item in component.Storage.ContainedEntities)
  178. {
  179. // use the same reagents as when we selected the recipe
  180. if (!_solutionContainer.TryGetDrainableSolution(item, out var solutionEntity, out var solution))
  181. continue;
  182. foreach (var (reagent, _) in recipe.IngredientsReagents)
  183. {
  184. // removed everything
  185. if (!totalReagentsToRemove.ContainsKey(reagent))
  186. continue;
  187. var quant = solution.GetTotalPrototypeQuantity(reagent);
  188. if (quant >= totalReagentsToRemove[reagent])
  189. {
  190. quant = totalReagentsToRemove[reagent];
  191. totalReagentsToRemove.Remove(reagent);
  192. }
  193. else
  194. {
  195. totalReagentsToRemove[reagent] -= quant;
  196. }
  197. _solutionContainer.RemoveReagent(solutionEntity.Value, reagent, quant);
  198. }
  199. }
  200. foreach (var recipeSolid in recipe.IngredientsSolids)
  201. {
  202. for (var i = 0; i < recipeSolid.Value; i++)
  203. {
  204. foreach (var item in component.Storage.ContainedEntities)
  205. {
  206. string? itemID = null;
  207. // If an entity has a stack component, use the stacktype instead of prototype id
  208. if (TryComp<StackComponent>(item, out var stackComp))
  209. {
  210. itemID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
  211. }
  212. else
  213. {
  214. var metaData = MetaData(item);
  215. if (metaData.EntityPrototype == null)
  216. {
  217. continue;
  218. }
  219. itemID = metaData.EntityPrototype.ID;
  220. }
  221. if (itemID != recipeSolid.Key)
  222. {
  223. continue;
  224. }
  225. if (stackComp is not null)
  226. {
  227. if (stackComp.Count == 1)
  228. {
  229. _container.Remove(item, component.Storage);
  230. }
  231. _stack.Use(item, 1, stackComp);
  232. break;
  233. }
  234. else
  235. {
  236. _container.Remove(item, component.Storage);
  237. Del(item);
  238. break;
  239. }
  240. }
  241. }
  242. }
  243. }
  244. private void OnInit(Entity<MicrowaveComponent> ent, ref ComponentInit args)
  245. {
  246. // this really does have to be in ComponentInit
  247. ent.Comp.Storage = _container.EnsureContainer<Container>(ent, ent.Comp.ContainerId);
  248. }
  249. private void OnMapInit(Entity<MicrowaveComponent> ent, ref MapInitEvent args)
  250. {
  251. _deviceLink.EnsureSinkPorts(ent, ent.Comp.OnPort);
  252. }
  253. /// <summary>
  254. /// Kills the user by microwaving their head
  255. /// TODO: Make this not awful, it keeps any items attached to your head still on and you can revive someone and cogni them so you have some dumb headless fuck running around. I've seen it happen.
  256. /// </summary>
  257. private void OnSuicideByEnvironment(Entity<MicrowaveComponent> ent, ref SuicideByEnvironmentEvent args)
  258. {
  259. if (args.Handled)
  260. return;
  261. // The act of getting your head microwaved doesn't actually kill you
  262. if (!TryComp<DamageableComponent>(args.Victim, out var damageableComponent))
  263. return;
  264. // The application of lethal damage is what kills you...
  265. _suicide.ApplyLethalDamage((args.Victim, damageableComponent), "Heat");
  266. var victim = args.Victim;
  267. var headCount = 0;
  268. if (TryComp<BodyComponent>(victim, out var body))
  269. {
  270. var headSlots = _bodySystem.GetBodyChildrenOfType(victim, BodyPartType.Head, body);
  271. foreach (var part in headSlots)
  272. {
  273. _container.Insert(part.Id, ent.Comp.Storage);
  274. headCount++;
  275. }
  276. }
  277. var othersMessage = headCount > 1
  278. ? Loc.GetString("microwave-component-suicide-multi-head-others-message", ("victim", victim))
  279. : Loc.GetString("microwave-component-suicide-others-message", ("victim", victim));
  280. var selfMessage = headCount > 1
  281. ? Loc.GetString("microwave-component-suicide-multi-head-message")
  282. : Loc.GetString("microwave-component-suicide-message");
  283. _popupSystem.PopupEntity(othersMessage, victim, Filter.PvsExcept(victim), true);
  284. _popupSystem.PopupEntity(selfMessage, victim, victim);
  285. _audio.PlayPvs(ent.Comp.ClickSound, ent.Owner, AudioParams.Default.WithVolume(-2));
  286. ent.Comp.CurrentCookTimerTime = 10;
  287. Wzhzhzh(ent.Owner, ent.Comp, args.Victim);
  288. UpdateUserInterfaceState(ent.Owner, ent.Comp);
  289. args.Handled = true;
  290. }
  291. private void OnSolutionChange(Entity<MicrowaveComponent> ent, ref SolutionContainerChangedEvent args)
  292. {
  293. UpdateUserInterfaceState(ent, ent.Comp);
  294. }
  295. private void OnContentUpdate(EntityUid uid, MicrowaveComponent component, ContainerModifiedMessage args) // For some reason ContainerModifiedMessage just can't be used at all with Entity<T>. TODO: replace with Entity<T> syntax once that's possible
  296. {
  297. if (component.Storage != args.Container)
  298. return;
  299. UpdateUserInterfaceState(uid, component);
  300. }
  301. private void OnInsertAttempt(Entity<MicrowaveComponent> ent, ref ContainerIsInsertingAttemptEvent args)
  302. {
  303. if (args.Container.ID != ent.Comp.ContainerId)
  304. return;
  305. if (ent.Comp.Broken)
  306. {
  307. args.Cancel();
  308. return;
  309. }
  310. if (TryComp<ItemComponent>(args.EntityUid, out var item))
  311. {
  312. if (_item.GetSizePrototype(item.Size) > _item.GetSizePrototype(ent.Comp.MaxItemSize))
  313. {
  314. args.Cancel();
  315. return;
  316. }
  317. }
  318. else
  319. {
  320. args.Cancel();
  321. return;
  322. }
  323. if (ent.Comp.Storage.Count >= ent.Comp.Capacity)
  324. args.Cancel();
  325. }
  326. private void OnInteractUsing(Entity<MicrowaveComponent> ent, ref InteractUsingEvent args)
  327. {
  328. if (args.Handled)
  329. return;
  330. if (!(TryComp<ApcPowerReceiverComponent>(ent, out var apc) && apc.Powered))
  331. {
  332. _popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-using-no-power"), ent, args.User);
  333. return;
  334. }
  335. if (ent.Comp.Broken)
  336. {
  337. _popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-using-broken"), ent, args.User);
  338. return;
  339. }
  340. if (TryComp<ItemComponent>(args.Used, out var item))
  341. {
  342. // check if size of an item you're trying to put in is too big
  343. if (_item.GetSizePrototype(item.Size) > _item.GetSizePrototype(ent.Comp.MaxItemSize))
  344. {
  345. _popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-item-too-big", ("item", args.Used)), ent, args.User);
  346. return;
  347. }
  348. }
  349. else
  350. {
  351. // check if thing you're trying to put in isn't an item
  352. _popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-using-transfer-fail"), ent, args.User);
  353. return;
  354. }
  355. if (ent.Comp.Storage.Count >= ent.Comp.Capacity)
  356. {
  357. _popupSystem.PopupEntity(Loc.GetString("microwave-component-interact-full"), ent, args.User);
  358. return;
  359. }
  360. args.Handled = true;
  361. _handsSystem.TryDropIntoContainer(args.User, args.Used, ent.Comp.Storage);
  362. UpdateUserInterfaceState(ent, ent.Comp);
  363. }
  364. private void OnBreak(Entity<MicrowaveComponent> ent, ref BreakageEventArgs args)
  365. {
  366. ent.Comp.Broken = true;
  367. SetAppearance(ent, MicrowaveVisualState.Broken, ent.Comp);
  368. StopCooking(ent);
  369. _container.EmptyContainer(ent.Comp.Storage);
  370. UpdateUserInterfaceState(ent, ent.Comp);
  371. }
  372. private void OnPowerChanged(Entity<MicrowaveComponent> ent, ref PowerChangedEvent args)
  373. {
  374. if (!args.Powered)
  375. {
  376. SetAppearance(ent, MicrowaveVisualState.Idle, ent.Comp);
  377. StopCooking(ent);
  378. }
  379. UpdateUserInterfaceState(ent, ent.Comp);
  380. }
  381. private void OnAnchorChanged(EntityUid uid, MicrowaveComponent component, ref AnchorStateChangedEvent args)
  382. {
  383. if (!args.Anchored)
  384. _container.EmptyContainer(component.Storage);
  385. }
  386. private void OnSignalReceived(Entity<MicrowaveComponent> ent, ref SignalReceivedEvent args)
  387. {
  388. if (args.Port != ent.Comp.OnPort)
  389. return;
  390. if (ent.Comp.Broken || !_power.IsPowered(ent))
  391. return;
  392. Wzhzhzh(ent.Owner, ent.Comp, null);
  393. }
  394. public void UpdateUserInterfaceState(EntityUid uid, MicrowaveComponent component)
  395. {
  396. _userInterface.SetUiState(uid, MicrowaveUiKey.Key, new MicrowaveUpdateUserInterfaceState(
  397. GetNetEntityArray(component.Storage.ContainedEntities.ToArray()),
  398. HasComp<ActiveMicrowaveComponent>(uid),
  399. component.CurrentCookTimeButtonIndex,
  400. component.CurrentCookTimerTime,
  401. component.CurrentCookTimeEnd
  402. ));
  403. }
  404. public void SetAppearance(EntityUid uid, MicrowaveVisualState state, MicrowaveComponent? component = null, AppearanceComponent? appearanceComponent = null)
  405. {
  406. if (!Resolve(uid, ref component, ref appearanceComponent, false))
  407. return;
  408. var display = component.Broken ? MicrowaveVisualState.Broken : state;
  409. _appearance.SetData(uid, PowerDeviceVisuals.VisualState, display, appearanceComponent);
  410. }
  411. public static bool HasContents(MicrowaveComponent component)
  412. {
  413. return component.Storage.ContainedEntities.Any();
  414. }
  415. /// <summary>
  416. /// Explodes the microwave internally, turning it into a broken state, destroying its board, and spitting out its machine parts
  417. /// </summary>
  418. /// <param name="ent"></param>
  419. public void Explode(Entity<MicrowaveComponent> ent)
  420. {
  421. ent.Comp.Broken = true; // Make broken so we stop processing stuff
  422. _explosion.TriggerExplosive(ent);
  423. if (TryComp<MachineComponent>(ent, out var machine))
  424. {
  425. _container.CleanContainer(machine.BoardContainer);
  426. _container.EmptyContainer(machine.PartContainer);
  427. }
  428. _adminLogger.Add(LogType.Action, LogImpact.Medium,
  429. $"{ToPrettyString(ent)} exploded from unsafe cooking!");
  430. }
  431. /// <summary>
  432. /// Handles the attempted cooking of unsafe objects
  433. /// </summary>
  434. /// <remarks>
  435. /// Returns false if the microwave didn't explode, true if it exploded.
  436. /// </remarks>
  437. private void RollMalfunction(Entity<ActiveMicrowaveComponent, MicrowaveComponent> ent)
  438. {
  439. if (ent.Comp1.MalfunctionTime == TimeSpan.Zero)
  440. return;
  441. if (ent.Comp1.MalfunctionTime > _gameTiming.CurTime)
  442. return;
  443. ent.Comp1.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(ent.Comp2.MalfunctionInterval);
  444. if (_random.Prob(ent.Comp2.ExplosionChance))
  445. {
  446. Explode((ent, ent.Comp2));
  447. return; // microwave is fucked, stop the cooking.
  448. }
  449. if (_random.Prob(ent.Comp2.LightningChance))
  450. _lightning.ShootRandomLightnings(ent, 1.0f, 2, MalfunctionSpark, triggerLightningEvents: false);
  451. }
  452. /// <summary>
  453. /// Starts Cooking
  454. /// </summary>
  455. /// <remarks>
  456. /// It does not make a "wzhzhzh" sound, it makes a "mmmmmmmm" sound!
  457. /// -emo
  458. /// </remarks>
  459. public void Wzhzhzh(EntityUid uid, MicrowaveComponent component, EntityUid? user)
  460. {
  461. if (!HasContents(component) || HasComp<ActiveMicrowaveComponent>(uid) || !(TryComp<ApcPowerReceiverComponent>(uid, out var apc) && apc.Powered))
  462. return;
  463. var solidsDict = new Dictionary<string, int>();
  464. var reagentDict = new Dictionary<string, FixedPoint2>();
  465. var malfunctioning = false;
  466. // TODO use lists of Reagent quantities instead of reagent prototype ids.
  467. foreach (var item in component.Storage.ContainedEntities.ToArray())
  468. {
  469. // special behavior when being microwaved ;)
  470. var ev = new BeingMicrowavedEvent(uid, user);
  471. RaiseLocalEvent(item, ev);
  472. if (ev.Handled)
  473. {
  474. UpdateUserInterfaceState(uid, component);
  475. return;
  476. }
  477. if (_tag.HasTag(item, "Metal"))
  478. {
  479. malfunctioning = true;
  480. }
  481. if (_tag.HasTag(item, "Plastic"))
  482. {
  483. var junk = Spawn(component.BadRecipeEntityId, Transform(uid).Coordinates);
  484. _container.Insert(junk, component.Storage);
  485. Del(item);
  486. continue;
  487. }
  488. var microwavedComp = AddComp<ActivelyMicrowavedComponent>(item);
  489. microwavedComp.Microwave = uid;
  490. string? solidID = null;
  491. int amountToAdd = 1;
  492. // If a microwave recipe uses a stacked item, use the default stack prototype id instead of prototype id
  493. if (TryComp<StackComponent>(item, out var stackComp))
  494. {
  495. solidID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
  496. amountToAdd = stackComp.Count;
  497. }
  498. else
  499. {
  500. var metaData = MetaData(item); //this simply begs for cooking refactor
  501. if (metaData.EntityPrototype is not null)
  502. solidID = metaData.EntityPrototype.ID;
  503. }
  504. if (solidID is null)
  505. continue;
  506. if (!solidsDict.TryAdd(solidID, amountToAdd))
  507. solidsDict[solidID] += amountToAdd;
  508. // only use reagents we have access to
  509. // you have to break the eggs before we can use them!
  510. if (!_solutionContainer.TryGetDrainableSolution(item, out var _, out var solution))
  511. continue;
  512. foreach (var (reagent, quantity) in solution.Contents)
  513. {
  514. if (!reagentDict.TryAdd(reagent.Prototype, quantity))
  515. reagentDict[reagent.Prototype] += quantity;
  516. }
  517. }
  518. // Check recipes
  519. var getRecipesEv = new GetSecretRecipesEvent();
  520. RaiseLocalEvent(uid, ref getRecipesEv);
  521. List<FoodRecipePrototype> recipes = getRecipesEv.Recipes;
  522. recipes.AddRange(_recipeManager.Recipes);
  523. var portionedRecipe = recipes.Select(r =>
  524. CanSatisfyRecipe(component, r, solidsDict, reagentDict)).FirstOrDefault(r => r.Item2 > 0);
  525. _audio.PlayPvs(component.StartCookingSound, uid);
  526. var activeComp = AddComp<ActiveMicrowaveComponent>(uid); //microwave is now cooking
  527. activeComp.CookTimeRemaining = component.CurrentCookTimerTime * component.CookTimeMultiplier;
  528. activeComp.TotalTime = component.CurrentCookTimerTime; //this doesn't scale so that we can have the "actual" time
  529. activeComp.PortionedRecipe = portionedRecipe;
  530. //Scale tiems with cook times
  531. component.CurrentCookTimeEnd = _gameTiming.CurTime + TimeSpan.FromSeconds(component.CurrentCookTimerTime * component.CookTimeMultiplier);
  532. if (malfunctioning)
  533. activeComp.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.MalfunctionInterval);
  534. UpdateUserInterfaceState(uid, component);
  535. }
  536. private void StopCooking(Entity<MicrowaveComponent> ent)
  537. {
  538. RemCompDeferred<ActiveMicrowaveComponent>(ent);
  539. foreach (var solid in ent.Comp.Storage.ContainedEntities)
  540. {
  541. RemCompDeferred<ActivelyMicrowavedComponent>(solid);
  542. }
  543. }
  544. public static (FoodRecipePrototype, int) CanSatisfyRecipe(MicrowaveComponent component, FoodRecipePrototype recipe, Dictionary<string, int> solids, Dictionary<string, FixedPoint2> reagents)
  545. {
  546. var portions = 0;
  547. if (component.CurrentCookTimerTime % recipe.CookTime != 0)
  548. {
  549. //can't be a multiple of this recipe
  550. return (recipe, 0);
  551. }
  552. foreach (var solid in recipe.IngredientsSolids)
  553. {
  554. if (!solids.ContainsKey(solid.Key))
  555. return (recipe, 0);
  556. if (solids[solid.Key] < solid.Value)
  557. return (recipe, 0);
  558. portions = portions == 0
  559. ? solids[solid.Key] / solid.Value.Int()
  560. : Math.Min(portions, solids[solid.Key] / solid.Value.Int());
  561. }
  562. foreach (var reagent in recipe.IngredientsReagents)
  563. {
  564. // TODO Turn recipe.IngredientsReagents into a ReagentQuantity[]
  565. if (!reagents.ContainsKey(reagent.Key))
  566. return (recipe, 0);
  567. if (reagents[reagent.Key] < reagent.Value)
  568. return (recipe, 0);
  569. portions = portions == 0
  570. ? reagents[reagent.Key].Int() / reagent.Value.Int()
  571. : Math.Min(portions, reagents[reagent.Key].Int() / reagent.Value.Int());
  572. }
  573. //cook only as many of those portions as time allows
  574. return (recipe, (int) Math.Min(portions, component.CurrentCookTimerTime / recipe.CookTime));
  575. }
  576. public override void Update(float frameTime)
  577. {
  578. base.Update(frameTime);
  579. var query = EntityQueryEnumerator<ActiveMicrowaveComponent, MicrowaveComponent>();
  580. while (query.MoveNext(out var uid, out var active, out var microwave))
  581. {
  582. active.CookTimeRemaining -= frameTime;
  583. RollMalfunction((uid, active, microwave));
  584. //check if there's still cook time left
  585. if (active.CookTimeRemaining > 0)
  586. {
  587. AddTemperature(microwave, frameTime);
  588. continue;
  589. }
  590. //this means the microwave has finished cooking.
  591. AddTemperature(microwave, Math.Max(frameTime + active.CookTimeRemaining, 0)); //Though there's still a little bit more heat to pump out
  592. if (active.PortionedRecipe.Item1 != null)
  593. {
  594. var coords = Transform(uid).Coordinates;
  595. for (var i = 0; i < active.PortionedRecipe.Item2; i++)
  596. {
  597. SubtractContents(microwave, active.PortionedRecipe.Item1);
  598. Spawn(active.PortionedRecipe.Item1.Result, coords);
  599. }
  600. }
  601. _container.EmptyContainer(microwave.Storage);
  602. microwave.CurrentCookTimeEnd = TimeSpan.Zero;
  603. UpdateUserInterfaceState(uid, microwave);
  604. _audio.PlayPvs(microwave.FoodDoneSound, uid);
  605. StopCooking((uid, microwave));
  606. }
  607. }
  608. /// <summary>
  609. /// This event tries to get secret recipes that the microwave might be capable of.
  610. /// Currently, we only check the microwave itself, but in the future, the user might be able to learn recipes.
  611. /// </summary>
  612. private void OnGetSecretRecipes(Entity<FoodRecipeProviderComponent> ent, ref GetSecretRecipesEvent args)
  613. {
  614. foreach (ProtoId<FoodRecipePrototype> recipeId in ent.Comp.ProvidedRecipes)
  615. {
  616. if (_prototype.TryIndex(recipeId, out var recipeProto))
  617. {
  618. args.Recipes.Add(recipeProto);
  619. }
  620. }
  621. }
  622. #region ui
  623. private void OnEjectMessage(Entity<MicrowaveComponent> ent, ref MicrowaveEjectMessage args)
  624. {
  625. if (!HasContents(ent.Comp) || HasComp<ActiveMicrowaveComponent>(ent))
  626. return;
  627. _container.EmptyContainer(ent.Comp.Storage);
  628. _audio.PlayPvs(ent.Comp.ClickSound, ent, AudioParams.Default.WithVolume(-2));
  629. UpdateUserInterfaceState(ent, ent.Comp);
  630. }
  631. private void OnEjectIndex(Entity<MicrowaveComponent> ent, ref MicrowaveEjectSolidIndexedMessage args)
  632. {
  633. if (!HasContents(ent.Comp) || HasComp<ActiveMicrowaveComponent>(ent))
  634. return;
  635. _container.Remove(EntityManager.GetEntity(args.EntityID), ent.Comp.Storage);
  636. UpdateUserInterfaceState(ent, ent.Comp);
  637. }
  638. private void OnSelectTime(Entity<MicrowaveComponent> ent, ref MicrowaveSelectCookTimeMessage args)
  639. {
  640. if (!HasContents(ent.Comp) || HasComp<ActiveMicrowaveComponent>(ent) || !(TryComp<ApcPowerReceiverComponent>(ent, out var apc) && apc.Powered))
  641. return;
  642. // some validation to prevent trollage
  643. if (args.NewCookTime % 5 != 0 || args.NewCookTime > ent.Comp.MaxCookTime)
  644. return;
  645. ent.Comp.CurrentCookTimeButtonIndex = args.ButtonIndex;
  646. ent.Comp.CurrentCookTimerTime = args.NewCookTime;
  647. ent.Comp.CurrentCookTimeEnd = TimeSpan.Zero;
  648. _audio.PlayPvs(ent.Comp.ClickSound, ent, AudioParams.Default.WithVolume(-2));
  649. UpdateUserInterfaceState(ent, ent.Comp);
  650. }
  651. #endregion
  652. }
  653. }