AbsorbentSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. using System.Numerics;
  2. using Content.Server.Popups;
  3. using Content.Shared.Chemistry.Components;
  4. using Content.Shared.Chemistry.EntitySystems;
  5. using Content.Shared.FixedPoint;
  6. using Content.Shared.Fluids;
  7. using Content.Shared.Fluids.Components;
  8. using Content.Shared.Interaction;
  9. using Content.Shared.Timing;
  10. using Content.Shared.Weapons.Melee;
  11. using Robust.Server.Audio;
  12. using Robust.Server.GameObjects;
  13. using Robust.Shared.Map.Components;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Utility;
  16. namespace Content.Server.Fluids.EntitySystems;
  17. /// <inheritdoc/>
  18. public sealed class AbsorbentSystem : SharedAbsorbentSystem
  19. {
  20. [Dependency] private readonly IPrototypeManager _prototype = default!;
  21. [Dependency] private readonly AudioSystem _audio = default!;
  22. [Dependency] private readonly PopupSystem _popups = default!;
  23. [Dependency] private readonly PuddleSystem _puddleSystem = default!;
  24. [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
  25. [Dependency] private readonly SharedTransformSystem _transform = default!;
  26. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  27. [Dependency] private readonly UseDelaySystem _useDelay = default!;
  28. [Dependency] private readonly MapSystem _mapSystem = default!;
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
  33. SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
  34. SubscribeLocalEvent<AbsorbentComponent, UserActivateInWorldEvent>(OnActivateInWorld);
  35. SubscribeLocalEvent<AbsorbentComponent, SolutionContainerChangedEvent>(OnAbsorbentSolutionChange);
  36. }
  37. private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
  38. {
  39. // TODO: I know dirty on init but no prediction moment.
  40. UpdateAbsorbent(uid, component);
  41. }
  42. private void OnAbsorbentSolutionChange(EntityUid uid, AbsorbentComponent component, ref SolutionContainerChangedEvent args)
  43. {
  44. UpdateAbsorbent(uid, component);
  45. }
  46. private void UpdateAbsorbent(EntityUid uid, AbsorbentComponent component)
  47. {
  48. if (!_solutionContainerSystem.TryGetSolution(uid, AbsorbentComponent.SolutionName, out _, out var solution))
  49. return;
  50. var oldProgress = component.Progress.ShallowClone();
  51. component.Progress.Clear();
  52. var water = solution.GetTotalPrototypeQuantity(PuddleSystem.EvaporationReagents);
  53. if (water > FixedPoint2.Zero)
  54. {
  55. component.Progress[solution.GetColorWithOnly(_prototype, PuddleSystem.EvaporationReagents)] = water.Float();
  56. }
  57. var otherColor = solution.GetColorWithout(_prototype, PuddleSystem.EvaporationReagents);
  58. var other = (solution.Volume - water).Float();
  59. if (other > 0f)
  60. {
  61. component.Progress[otherColor] = other;
  62. }
  63. var remainder = solution.AvailableVolume;
  64. if (remainder > FixedPoint2.Zero)
  65. {
  66. component.Progress[Color.DarkGray] = remainder.Float();
  67. }
  68. if (component.Progress.Equals(oldProgress))
  69. return;
  70. Dirty(uid, component);
  71. }
  72. private void OnActivateInWorld(EntityUid uid, AbsorbentComponent component, UserActivateInWorldEvent args)
  73. {
  74. if (args.Handled)
  75. return;
  76. Mop(uid, args.Target, uid, component);
  77. args.Handled = true;
  78. }
  79. private void OnAfterInteract(EntityUid uid, AbsorbentComponent component, AfterInteractEvent args)
  80. {
  81. if (!args.CanReach || args.Handled || args.Target == null)
  82. return;
  83. Mop(args.User, args.Target.Value, args.Used, component);
  84. args.Handled = true;
  85. }
  86. public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component)
  87. {
  88. if (!_solutionContainerSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln))
  89. return;
  90. if (TryComp<UseDelayComponent>(used, out var useDelay)
  91. && _useDelay.IsDelayed((used, useDelay)))
  92. return;
  93. // If it's a puddle try to grab from
  94. if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value))
  95. {
  96. // If it's refillable try to transfer
  97. if (!TryRefillableInteract(user, used, target, component, useDelay, absorberSoln.Value))
  98. return;
  99. }
  100. }
  101. /// <summary>
  102. /// Logic for an absorbing entity interacting with a refillable.
  103. /// </summary>
  104. private bool TryRefillableInteract(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, UseDelayComponent? useDelay, Entity<SolutionComponent> absorbentSoln)
  105. {
  106. if (!TryComp(target, out RefillableSolutionComponent? refillable))
  107. return false;
  108. if (!_solutionContainerSystem.TryGetRefillableSolution((target, refillable, null), out var refillableSoln, out var refillableSolution))
  109. return false;
  110. if (refillableSolution.Volume <= 0)
  111. {
  112. // Target empty - only transfer absorbent contents into refillable
  113. if (!TryTransferFromAbsorbentToRefillable(user, used, target, component, absorbentSoln, refillableSoln.Value))
  114. return false;
  115. }
  116. else
  117. {
  118. // Target non-empty - do a two-way transfer
  119. if (!TryTwoWayAbsorbentRefillableTransfer(user, used, target, component, absorbentSoln, refillableSoln.Value))
  120. return false;
  121. }
  122. _audio.PlayPvs(component.TransferSound, target);
  123. if (useDelay != null)
  124. _useDelay.TryResetDelay((used, useDelay));
  125. return true;
  126. }
  127. /// <summary>
  128. /// Logic for an transferring solution from absorber to an empty refillable.
  129. /// </summary>
  130. private bool TryTransferFromAbsorbentToRefillable(
  131. EntityUid user,
  132. EntityUid used,
  133. EntityUid target,
  134. AbsorbentComponent component,
  135. Entity<SolutionComponent> absorbentSoln,
  136. Entity<SolutionComponent> refillableSoln)
  137. {
  138. var absorbentSolution = absorbentSoln.Comp.Solution;
  139. if (absorbentSolution.Volume <= 0)
  140. {
  141. _popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty", ("target", target)), user, user);
  142. return false;
  143. }
  144. var refillableSolution = refillableSoln.Comp.Solution;
  145. var transferAmount = component.PickupAmount < refillableSolution.AvailableVolume ?
  146. component.PickupAmount :
  147. refillableSolution.AvailableVolume;
  148. if (transferAmount <= 0)
  149. {
  150. _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", used)), used, user);
  151. return false;
  152. }
  153. // Prioritize transferring non-evaporatives if absorbent has any
  154. var contaminants = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, transferAmount, PuddleSystem.EvaporationReagents);
  155. if (contaminants.Volume > 0)
  156. {
  157. _solutionContainerSystem.TryAddSolution(refillableSoln, contaminants);
  158. }
  159. else
  160. {
  161. var evaporatives = _solutionContainerSystem.SplitSolution(absorbentSoln, transferAmount);
  162. _solutionContainerSystem.TryAddSolution(refillableSoln, evaporatives);
  163. }
  164. return true;
  165. }
  166. /// <summary>
  167. /// Logic for an transferring contaminants to a non-empty refillable & reabsorbing water if any available.
  168. /// </summary>
  169. private bool TryTwoWayAbsorbentRefillableTransfer(
  170. EntityUid user,
  171. EntityUid used,
  172. EntityUid target,
  173. AbsorbentComponent component,
  174. Entity<SolutionComponent> absorbentSoln,
  175. Entity<SolutionComponent> refillableSoln)
  176. {
  177. var contaminantsFromAbsorbent = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, component.PickupAmount, PuddleSystem.EvaporationReagents);
  178. var absorbentSolution = absorbentSoln.Comp.Solution;
  179. if (contaminantsFromAbsorbent.Volume == FixedPoint2.Zero && absorbentSolution.AvailableVolume == FixedPoint2.Zero)
  180. {
  181. // Nothing to transfer to refillable and no room to absorb anything extra
  182. _popups.PopupEntity(Loc.GetString("mopping-system-puddle-space", ("used", used)), user, user);
  183. // We can return cleanly because nothing was split from absorbent solution
  184. return false;
  185. }
  186. var waterPulled = component.PickupAmount < absorbentSolution.AvailableVolume ?
  187. component.PickupAmount :
  188. absorbentSolution.AvailableVolume;
  189. var refillableSolution = refillableSoln.Comp.Solution;
  190. var waterFromRefillable = refillableSolution.SplitSolutionWithOnly(waterPulled, PuddleSystem.EvaporationReagents);
  191. _solutionContainerSystem.UpdateChemicals(refillableSoln);
  192. if (waterFromRefillable.Volume == FixedPoint2.Zero && contaminantsFromAbsorbent.Volume == FixedPoint2.Zero)
  193. {
  194. // Nothing to transfer in either direction
  195. _popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty-water", ("target", target)), user, user);
  196. // We can return cleanly because nothing was split from refillable solution
  197. return false;
  198. }
  199. var anyTransferOccurred = false;
  200. if (waterFromRefillable.Volume > FixedPoint2.Zero)
  201. {
  202. // transfer water to absorbent
  203. _solutionContainerSystem.TryAddSolution(absorbentSoln, waterFromRefillable);
  204. anyTransferOccurred = true;
  205. }
  206. if (contaminantsFromAbsorbent.Volume > 0)
  207. {
  208. if (refillableSolution.AvailableVolume <= 0)
  209. {
  210. _popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", target)), user, user);
  211. }
  212. else
  213. {
  214. // transfer as much contaminants to refillable as will fit
  215. var contaminantsForRefillable = contaminantsFromAbsorbent.SplitSolution(refillableSolution.AvailableVolume);
  216. _solutionContainerSystem.TryAddSolution(refillableSoln, contaminantsForRefillable);
  217. anyTransferOccurred = true;
  218. }
  219. // absorb everything that did not fit in the refillable back by the absorbent
  220. _solutionContainerSystem.TryAddSolution(absorbentSoln, contaminantsFromAbsorbent);
  221. }
  222. return anyTransferOccurred;
  223. }
  224. /// <summary>
  225. /// Logic for an absorbing entity interacting with a puddle.
  226. /// </summary>
  227. private bool TryPuddleInteract(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent absorber, UseDelayComponent? useDelay, Entity<SolutionComponent> absorberSoln)
  228. {
  229. if (!TryComp(target, out PuddleComponent? puddle))
  230. return false;
  231. if (!_solutionContainerSystem.ResolveSolution(target, puddle.SolutionName, ref puddle.Solution, out var puddleSolution) || puddleSolution.Volume <= 0)
  232. return false;
  233. // Check if the puddle has any non-evaporative reagents
  234. if (_puddleSystem.CanFullyEvaporate(puddleSolution))
  235. {
  236. _popups.PopupEntity(Loc.GetString("mopping-system-puddle-evaporate", ("target", target)), user, user);
  237. return true;
  238. }
  239. // Check if we have any evaporative reagents on our absorber to transfer
  240. var absorberSolution = absorberSoln.Comp.Solution;
  241. var available = absorberSolution.GetTotalPrototypeQuantity(PuddleSystem.EvaporationReagents);
  242. // No material
  243. if (available == FixedPoint2.Zero)
  244. {
  245. _popups.PopupEntity(Loc.GetString("mopping-system-no-water", ("used", used)), user, user);
  246. return true;
  247. }
  248. var transferMax = absorber.PickupAmount;
  249. var transferAmount = available > transferMax ? transferMax : available;
  250. var puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, PuddleSystem.EvaporationReagents);
  251. var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, PuddleSystem.EvaporationReagents);
  252. // Do tile reactions first
  253. var transform = Transform(target);
  254. var gridUid = transform.GridUid;
  255. if (TryComp(gridUid, out MapGridComponent? mapGrid))
  256. {
  257. var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates);
  258. _puddleSystem.DoTileReactions(tileRef, absorberSplit);
  259. }
  260. _solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit);
  261. _solutionContainerSystem.AddSolution(absorberSoln, puddleSplit);
  262. _audio.PlayPvs(absorber.PickupSound, target);
  263. if (useDelay != null)
  264. _useDelay.TryResetDelay((used, useDelay));
  265. var userXform = Transform(user);
  266. var targetPos = _transform.GetWorldPosition(target);
  267. var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform));
  268. localPos = userXform.LocalRotation.RotateVec(localPos);
  269. _melee.DoLunge(user, used, Angle.Zero, localPos, null, false);
  270. return true;
  271. }
  272. }