1
0

DrainSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using Content.Server.DoAfter;
  2. using Content.Server.Popups;
  3. using Content.Shared.Chemistry.EntitySystems;
  4. using Content.Shared.Audio;
  5. using Content.Shared.Chemistry.Components.SolutionManager;
  6. using Content.Shared.Database;
  7. using Content.Shared.DoAfter;
  8. using Content.Shared.Examine;
  9. using Content.Shared.FixedPoint;
  10. using Content.Shared.Fluids;
  11. using Content.Shared.Fluids.Components;
  12. using Content.Shared.Interaction;
  13. using Content.Shared.Tag;
  14. using Content.Shared.Verbs;
  15. using Robust.Shared.Audio.Systems;
  16. using Robust.Shared.Prototypes;
  17. using Robust.Shared.Random;
  18. using Robust.Shared.Utility;
  19. namespace Content.Server.Fluids.EntitySystems;
  20. public sealed class DrainSystem : SharedDrainSystem
  21. {
  22. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  23. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  24. [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
  25. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  26. [Dependency] private readonly PopupSystem _popupSystem = default!;
  27. [Dependency] private readonly TagSystem _tagSystem = default!;
  28. [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
  29. [Dependency] private readonly PuddleSystem _puddleSystem = default!;
  30. [Dependency] private readonly IRobustRandom _random = default!;
  31. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  32. private readonly HashSet<Entity<PuddleComponent>> _puddles = new();
  33. public override void Initialize()
  34. {
  35. base.Initialize();
  36. SubscribeLocalEvent<DrainComponent, MapInitEvent>(OnDrainMapInit);
  37. SubscribeLocalEvent<DrainComponent, GetVerbsEvent<Verb>>(AddEmptyVerb);
  38. SubscribeLocalEvent<DrainComponent, ExaminedEvent>(OnExamined);
  39. SubscribeLocalEvent<DrainComponent, AfterInteractUsingEvent>(OnInteract);
  40. SubscribeLocalEvent<DrainComponent, DrainDoAfterEvent>(OnDoAfter);
  41. }
  42. private void OnDrainMapInit(Entity<DrainComponent> ent, ref MapInitEvent args)
  43. {
  44. // Randomise puddle drains so roundstart ones don't all dump at the same time.
  45. ent.Comp.Accumulator = _random.NextFloat(ent.Comp.DrainFrequency);
  46. }
  47. private void AddEmptyVerb(Entity<DrainComponent> entity, ref GetVerbsEvent<Verb> args)
  48. {
  49. if (!args.CanAccess || !args.CanInteract || args.Using == null)
  50. return;
  51. if (!TryComp(args.Using, out SpillableComponent? spillable) ||
  52. !TryComp(args.Target, out DrainComponent? drain))
  53. return;
  54. var used = args.Using.Value;
  55. var target = args.Target;
  56. Verb verb = new()
  57. {
  58. Text = Loc.GetString("drain-component-empty-verb-inhand", ("object", Name(used))),
  59. Act = () =>
  60. {
  61. Empty(used, spillable, target, drain);
  62. },
  63. Impact = LogImpact.Low,
  64. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png"))
  65. };
  66. args.Verbs.Add(verb);
  67. }
  68. private void Empty(EntityUid container, SpillableComponent spillable, EntityUid target, DrainComponent drain)
  69. {
  70. // Find the solution in the container that is emptied
  71. if (!_solutionContainerSystem.TryGetDrainableSolution(container, out var containerSoln, out var containerSolution) || containerSolution.Volume == FixedPoint2.Zero)
  72. {
  73. _popupSystem.PopupEntity(
  74. Loc.GetString("drain-component-empty-verb-using-is-empty-message", ("object", container)),
  75. container);
  76. return;
  77. }
  78. // try to find the drain's solution
  79. if (!_solutionContainerSystem.ResolveSolution(target, DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
  80. {
  81. return;
  82. }
  83. // Try to transfer as much solution as possible to the drain
  84. var amountToPutInDrain = drainSolution.AvailableVolume;
  85. var amountToSpillOnGround = containerSolution.Volume - drainSolution.AvailableVolume;
  86. if (amountToPutInDrain > 0)
  87. {
  88. var solutionToPutInDrain = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToPutInDrain);
  89. _solutionContainerSystem.TryAddSolution(drain.Solution.Value, solutionToPutInDrain);
  90. _audioSystem.PlayPvs(drain.ManualDrainSound, target);
  91. _ambientSoundSystem.SetAmbience(target, true);
  92. }
  93. // Spill the remainder.
  94. if (amountToSpillOnGround > 0)
  95. {
  96. var solutionToSpill = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToSpillOnGround);
  97. _puddleSystem.TrySpillAt(Transform(target).Coordinates, solutionToSpill, out _);
  98. _popupSystem.PopupEntity(
  99. Loc.GetString("drain-component-empty-verb-target-is-full-message", ("object", target)),
  100. container);
  101. }
  102. }
  103. public override void Update(float frameTime)
  104. {
  105. base.Update(frameTime);
  106. var managerQuery = GetEntityQuery<SolutionContainerManagerComponent>();
  107. var query = EntityQueryEnumerator<DrainComponent>();
  108. while (query.MoveNext(out var uid, out var drain))
  109. {
  110. drain.Accumulator += frameTime;
  111. if (drain.Accumulator < drain.DrainFrequency)
  112. {
  113. continue;
  114. }
  115. drain.Accumulator -= drain.DrainFrequency;
  116. if (!managerQuery.TryGetComponent(uid, out var manager))
  117. continue;
  118. // Best to do this one every second rather than once every tick...
  119. if (!_solutionContainerSystem.ResolveSolution((uid, manager), DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
  120. continue;
  121. if (drainSolution.Volume <= 0 && !drain.AutoDrain)
  122. {
  123. _ambientSoundSystem.SetAmbience(uid, false);
  124. continue;
  125. }
  126. // Remove a bit from the buffer
  127. _solutionContainerSystem.SplitSolution(drain.Solution.Value, (drain.UnitsDestroyedPerSecond * drain.DrainFrequency));
  128. // This will ensure that UnitsPerSecond is per second...
  129. var amount = drain.UnitsPerSecond * drain.DrainFrequency;
  130. if (drain.AutoDrain)
  131. {
  132. _puddles.Clear();
  133. _lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
  134. if (_puddles.Count == 0 && drainSolution.Volume <= 0)
  135. {
  136. _ambientSoundSystem.SetAmbience(uid, false);
  137. continue;
  138. }
  139. _ambientSoundSystem.SetAmbience(uid, true);
  140. amount /= _puddles.Count;
  141. foreach (var puddle in _puddles)
  142. {
  143. // Queue the solution deletion if it's empty. EvaporationSystem might also do this
  144. // but queuedelete should be pretty safe.
  145. if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
  146. {
  147. EntityManager.QueueDeleteEntity(puddle);
  148. continue;
  149. }
  150. // Removes the lowest of:
  151. // the drain component's units per second adjusted for # of puddles
  152. // the puddle's remaining volume (making it cleanly zero)
  153. // the drain's remaining volume in its buffer.
  154. var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
  155. FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
  156. drainSolution.AddSolution(transferSolution, _prototypeManager);
  157. if (puddleSolution.Volume <= 0)
  158. {
  159. QueueDel(puddle);
  160. }
  161. }
  162. }
  163. _solutionContainerSystem.UpdateChemicals(drain.Solution.Value);
  164. }
  165. }
  166. private void OnExamined(Entity<DrainComponent> entity, ref ExaminedEvent args)
  167. {
  168. if (!args.IsInDetailsRange ||
  169. !HasComp<SolutionContainerManagerComponent>(entity) ||
  170. !_solutionContainerSystem.ResolveSolution(entity.Owner, DrainComponent.SolutionName, ref entity.Comp.Solution, out var drainSolution))
  171. {
  172. return;
  173. }
  174. var text = drainSolution.AvailableVolume != 0
  175. ? Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume))
  176. : Loc.GetString("drain-component-examine-hint-full");
  177. args.PushMarkup(text);
  178. }
  179. private void OnInteract(Entity<DrainComponent> entity, ref AfterInteractUsingEvent args)
  180. {
  181. if (!args.CanReach || args.Target == null ||
  182. !_tagSystem.HasTag(args.Used, DrainComponent.PlungerTag) ||
  183. !_solutionContainerSystem.ResolveSolution(args.Target.Value, DrainComponent.SolutionName, ref entity.Comp.Solution, out var drainSolution))
  184. {
  185. return;
  186. }
  187. if (drainSolution.AvailableVolume > 0)
  188. {
  189. _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-notapplicable", ("object", args.Target.Value)), args.Target.Value);
  190. return;
  191. }
  192. _audioSystem.PlayPvs(entity.Comp.PlungerSound, entity);
  193. var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.UnclogDuration, new DrainDoAfterEvent(), entity, args.Target, args.Used)
  194. {
  195. BreakOnDamage = true,
  196. BreakOnMove = true,
  197. BreakOnHandChange = true
  198. };
  199. _doAfterSystem.TryStartDoAfter(doAfterArgs);
  200. }
  201. private void OnDoAfter(Entity<DrainComponent> entity, ref DrainDoAfterEvent args)
  202. {
  203. if (args.Target == null)
  204. return;
  205. if (!_random.Prob(entity.Comp.UnclogProbability))
  206. {
  207. _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-fail", ("object", args.Target.Value)), args.Target.Value);
  208. return;
  209. }
  210. if (!_solutionContainerSystem.ResolveSolution(args.Target.Value, DrainComponent.SolutionName, ref entity.Comp.Solution))
  211. {
  212. return;
  213. }
  214. _solutionContainerSystem.RemoveAllSolution(entity.Comp.Solution.Value);
  215. _audioSystem.PlayPvs(entity.Comp.UnclogSound, args.Target.Value);
  216. _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-success", ("object", args.Target.Value)), args.Target.Value);
  217. }
  218. }