1
0

SharedPuddleSystem.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. using Content.Shared.Chemistry.Components;
  2. using Content.Shared.Chemistry.EntitySystems;
  3. using Content.Shared.Chemistry.Reagent;
  4. using Content.Shared.DoAfter;
  5. using Content.Shared.DragDrop;
  6. using Content.Shared.Examine;
  7. using Content.Shared.FixedPoint;
  8. using Content.Shared.Fluids.Components;
  9. using Content.Shared.Movement.Events;
  10. using Content.Shared.StepTrigger.Components;
  11. using Robust.Shared.Map;
  12. using Robust.Shared.Prototypes;
  13. namespace Content.Shared.Fluids;
  14. public abstract partial class SharedPuddleSystem : EntitySystem
  15. {
  16. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  17. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  18. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  19. /// <summary>
  20. /// The lowest threshold to be considered for puddle sprite states as well as slipperiness of a puddle.
  21. /// </summary>
  22. public const float LowThreshold = 0.3f;
  23. public const float MediumThreshold = 0.6f;
  24. public override void Initialize()
  25. {
  26. base.Initialize();
  27. SubscribeLocalEvent<RefillableSolutionComponent, CanDragEvent>(OnRefillableCanDrag);
  28. SubscribeLocalEvent<DumpableSolutionComponent, CanDropTargetEvent>(OnDumpCanDropTarget);
  29. SubscribeLocalEvent<DrainableSolutionComponent, CanDropTargetEvent>(OnDrainCanDropTarget);
  30. SubscribeLocalEvent<RefillableSolutionComponent, CanDropDraggedEvent>(OnRefillableCanDropDragged);
  31. SubscribeLocalEvent<PuddleComponent, GetFootstepSoundEvent>(OnGetFootstepSound);
  32. SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined);
  33. InitializeSpillable();
  34. }
  35. private void OnRefillableCanDrag(Entity<RefillableSolutionComponent> entity, ref CanDragEvent args)
  36. {
  37. args.Handled = true;
  38. }
  39. private void OnDumpCanDropTarget(Entity<DumpableSolutionComponent> entity, ref CanDropTargetEvent args)
  40. {
  41. if (HasComp<DrainableSolutionComponent>(args.Dragged))
  42. {
  43. args.CanDrop = true;
  44. args.Handled = true;
  45. }
  46. }
  47. private void OnDrainCanDropTarget(Entity<DrainableSolutionComponent> entity, ref CanDropTargetEvent args)
  48. {
  49. if (HasComp<RefillableSolutionComponent>(args.Dragged))
  50. {
  51. args.CanDrop = true;
  52. args.Handled = true;
  53. }
  54. }
  55. private void OnRefillableCanDropDragged(Entity<RefillableSolutionComponent> entity, ref CanDropDraggedEvent args)
  56. {
  57. if (!HasComp<DrainableSolutionComponent>(args.Target) && !HasComp<DumpableSolutionComponent>(args.Target))
  58. return;
  59. args.CanDrop = true;
  60. args.Handled = true;
  61. }
  62. private void OnGetFootstepSound(Entity<PuddleComponent> entity, ref GetFootstepSoundEvent args)
  63. {
  64. if (!_solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution,
  65. out var solution))
  66. return;
  67. var reagentId = solution.GetPrimaryReagentId();
  68. if (!string.IsNullOrWhiteSpace(reagentId?.Prototype)
  69. && _prototypeManager.TryIndex(reagentId.Value.Prototype, out ReagentPrototype? proto))
  70. {
  71. args.Sound = proto.FootstepSound;
  72. }
  73. }
  74. private void HandlePuddleExamined(Entity<PuddleComponent> entity, ref ExaminedEvent args)
  75. {
  76. using (args.PushGroup(nameof(PuddleComponent)))
  77. {
  78. if (TryComp<StepTriggerComponent>(entity, out var slippery) && slippery.Active)
  79. {
  80. args.PushMarkup(Loc.GetString("puddle-component-examine-is-slippery-text"));
  81. }
  82. if (HasComp<EvaporationComponent>(entity) &&
  83. _solutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.SolutionName,
  84. ref entity.Comp.Solution, out var solution))
  85. {
  86. if (CanFullyEvaporate(solution))
  87. args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating"));
  88. else if (solution.GetTotalPrototypeQuantity(EvaporationReagents) > FixedPoint2.Zero)
  89. args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-partial"));
  90. else
  91. args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
  92. }
  93. else
  94. args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
  95. }
  96. }
  97. #region Spill
  98. // These methods are in Shared to make it easier to interact with PuddleSystem in Shared code.
  99. // Note that they always fail when run on the client, not creating a puddle and returning false.
  100. // Adding proper prediction to this system would require spawning temporary puddle entities on the
  101. // client and replacing or merging them with the ones spawned by the server when the client goes to
  102. // replicate those, and I am not enough of a wizard to attempt implementing that.
  103. /// <summary>
  104. /// First splashes reagent on reactive entities near the spilling entity, then spills the rest regularly to a
  105. /// puddle. This is intended for 'destructive' spills, like when entities are destroyed or thrown.
  106. /// </summary>
  107. /// <remarks>
  108. /// On the client, this will always set <paramref name="puddleUid"/> to <see cref="EntityUid.Invalid"> and return false.
  109. /// </remarks>
  110. public abstract bool TrySplashSpillAt(EntityUid uid,
  111. EntityCoordinates coordinates,
  112. Solution solution,
  113. out EntityUid puddleUid,
  114. bool sound = true,
  115. EntityUid? user = null);
  116. /// <summary>
  117. /// Spills solution at the specified coordinates.
  118. /// Will add to an existing puddle if present or create a new one if not.
  119. /// </summary>
  120. /// <remarks>
  121. /// On the client, this will always set <paramref name="puddleUid"/> to <see cref="EntityUid.Invalid"> and return false.
  122. /// </remarks>
  123. public abstract bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true);
  124. /// <summary>
  125. /// <see cref="TrySpillAt(EntityCoordinates, Solution, out EntityUid, bool)"/>
  126. /// </summary>
  127. /// <remarks>
  128. /// On the client, this will always set <paramref name="puddleUid"/> to <see cref="EntityUid.Invalid"> and return false.
  129. /// </remarks>
  130. public abstract bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true,
  131. TransformComponent? transformComponent = null);
  132. /// <summary>
  133. /// <see cref="TrySpillAt(EntityCoordinates, Solution, out EntityUid, bool)"/>
  134. /// </summary>
  135. /// <remarks>
  136. /// On the client, this will always set <paramref name="puddleUid"/> to <see cref="EntityUid.Invalid"> and return false.
  137. /// </remarks>
  138. public abstract bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true,
  139. bool tileReact = true);
  140. #endregion Spill
  141. }