1
0

PuddleSystem.Spillable.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. using Content.Server.Chemistry.Containers.EntitySystems;
  2. using Content.Shared.Chemistry.Components;
  3. using Content.Shared.Chemistry.EntitySystems;
  4. using Content.Shared.Chemistry.Reaction;
  5. using Content.Shared.Chemistry;
  6. using Content.Shared.Clothing;
  7. using Content.Shared.CombatMode.Pacification;
  8. using Content.Shared.Database;
  9. using Content.Shared.FixedPoint;
  10. using Content.Shared.Fluids.Components;
  11. using Content.Shared.IdentityManagement;
  12. using Content.Shared.Nutrition.EntitySystems;
  13. using Content.Shared.Popups;
  14. using Content.Shared.Spillable;
  15. using Content.Shared.Throwing;
  16. using Content.Shared.Weapons.Melee.Events;
  17. using Robust.Shared.Player;
  18. namespace Content.Server.Fluids.EntitySystems;
  19. public sealed partial class PuddleSystem
  20. {
  21. protected override void InitializeSpillable()
  22. {
  23. base.InitializeSpillable();
  24. SubscribeLocalEvent<SpillableComponent, LandEvent>(SpillOnLand);
  25. // Openable handles the event if it's closed
  26. SubscribeLocalEvent<SpillableComponent, MeleeHitEvent>(SplashOnMeleeHit, after: [typeof(OpenableSystem)]);
  27. SubscribeLocalEvent<SpillableComponent, SolutionContainerOverflowEvent>(OnOverflow);
  28. SubscribeLocalEvent<SpillableComponent, SpillDoAfterEvent>(OnDoAfter);
  29. SubscribeLocalEvent<SpillableComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
  30. }
  31. private void OnOverflow(Entity<SpillableComponent> entity, ref SolutionContainerOverflowEvent args)
  32. {
  33. if (args.Handled)
  34. return;
  35. TrySpillAt(Transform(entity).Coordinates, args.Overflow, out _);
  36. args.Handled = true;
  37. }
  38. private void SplashOnMeleeHit(Entity<SpillableComponent> entity, ref MeleeHitEvent args)
  39. {
  40. if (args.Handled)
  41. return;
  42. // When attacking someone reactive with a spillable entity,
  43. // splash a little on them (touch react)
  44. // If this also has solution transfer, then assume the transfer amount is how much we want to spill.
  45. // Otherwise let's say they want to spill a quarter of its max volume.
  46. if (!_solutionContainerSystem.TryGetDrainableSolution(entity.Owner, out var soln, out var solution))
  47. return;
  48. var hitCount = args.HitEntities.Count;
  49. var totalSplit = FixedPoint2.Min(solution.MaxVolume * 0.25, solution.Volume);
  50. if (TryComp<SolutionTransferComponent>(entity, out var transfer))
  51. {
  52. totalSplit = FixedPoint2.Min(transfer.TransferAmount, solution.Volume);
  53. }
  54. // a little lame, but reagent quantity is not very balanced and we don't want people
  55. // spilling like 100u of reagent on someone at once!
  56. totalSplit = FixedPoint2.Min(totalSplit, entity.Comp.MaxMeleeSpillAmount);
  57. if (totalSplit == 0)
  58. return;
  59. args.Handled = true;
  60. // First update the hit count so anything that is not reactive wont count towards the total!
  61. foreach (var hit in args.HitEntities)
  62. {
  63. if (!HasComp<ReactiveComponent>(hit))
  64. hitCount -= 1;
  65. }
  66. foreach (var hit in args.HitEntities)
  67. {
  68. if (!HasComp<ReactiveComponent>(hit))
  69. continue;
  70. var splitSolution = _solutionContainerSystem.SplitSolution(soln.Value, totalSplit / hitCount);
  71. _adminLogger.Add(LogType.MeleeHit, $"{ToPrettyString(args.User)} splashed {SharedSolutionContainerSystem.ToPrettyString(splitSolution):solution} from {ToPrettyString(entity.Owner):entity} onto {ToPrettyString(hit):target}");
  72. _reactive.DoEntityReaction(hit, splitSolution, ReactionMethod.Touch);
  73. _popups.PopupEntity(
  74. Loc.GetString("spill-melee-hit-attacker", ("amount", totalSplit / hitCount), ("spillable", entity.Owner),
  75. ("target", Identity.Entity(hit, EntityManager))),
  76. hit, args.User);
  77. _popups.PopupEntity(
  78. Loc.GetString("spill-melee-hit-others", ("attacker", args.User), ("spillable", entity.Owner),
  79. ("target", Identity.Entity(hit, EntityManager))),
  80. hit, Filter.PvsExcept(args.User), true, PopupType.SmallCaution);
  81. }
  82. }
  83. private void SpillOnLand(Entity<SpillableComponent> entity, ref LandEvent args)
  84. {
  85. if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution))
  86. return;
  87. if (Openable.IsClosed(entity.Owner))
  88. return;
  89. if (!entity.Comp.SpillWhenThrown)
  90. return;
  91. if (args.User != null)
  92. {
  93. _adminLogger.Add(LogType.Landed,
  94. $"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
  95. }
  96. var drainedSolution = _solutionContainerSystem.Drain(entity.Owner, soln.Value, solution.Volume);
  97. TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, drainedSolution, out _);
  98. }
  99. /// <summary>
  100. /// Prevent Pacified entities from throwing items that can spill liquids.
  101. /// </summary>
  102. private void OnAttemptPacifiedThrow(Entity<SpillableComponent> ent, ref AttemptPacifiedThrowEvent args)
  103. {
  104. // Don’t care about closed containers.
  105. if (Openable.IsClosed(ent))
  106. return;
  107. // Don’t care about empty containers.
  108. if (!_solutionContainerSystem.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out _, out var solution) || solution.Volume <= 0)
  109. return;
  110. args.Cancel("pacified-cannot-throw-spill");
  111. }
  112. private void OnDoAfter(Entity<SpillableComponent> entity, ref SpillDoAfterEvent args)
  113. {
  114. if (args.Handled || args.Cancelled || args.Args.Target == null)
  115. return;
  116. //solution gone by other means before doafter completes
  117. if (!_solutionContainerSystem.TryGetDrainableSolution(entity.Owner, out var soln, out var solution) || solution.Volume == 0)
  118. return;
  119. var puddleSolution = _solutionContainerSystem.SplitSolution(soln.Value, solution.Volume);
  120. TrySpillAt(Transform(entity).Coordinates, puddleSolution, out _);
  121. args.Handled = true;
  122. }
  123. }