ThirstSystem.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. using Content.Shared.Alert;
  2. using Content.Shared.Movement.Components;
  3. using Content.Shared.Movement.Systems;
  4. using Content.Shared.Nutrition.Components;
  5. using Content.Shared.Rejuvenate;
  6. using Content.Shared.StatusIcon;
  7. using JetBrains.Annotations;
  8. using Robust.Shared.Prototypes;
  9. using Robust.Shared.Random;
  10. using Robust.Shared.Timing;
  11. using Robust.Shared.Utility;
  12. using System.Diagnostics.CodeAnalysis;
  13. namespace Content.Shared.Nutrition.EntitySystems;
  14. [UsedImplicitly]
  15. public sealed class ThirstSystem : EntitySystem
  16. {
  17. [Dependency] private readonly IGameTiming _timing = default!;
  18. [Dependency] private readonly IPrototypeManager _prototype = default!;
  19. [Dependency] private readonly IRobustRandom _random = default!;
  20. [Dependency] private readonly AlertsSystem _alerts = default!;
  21. [Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
  22. [Dependency] private readonly SharedJetpackSystem _jetpack = default!;
  23. [ValidatePrototypeId<SatiationIconPrototype>]
  24. private const string ThirstIconOverhydratedId = "ThirstIconOverhydrated";
  25. [ValidatePrototypeId<SatiationIconPrototype>]
  26. private const string ThirstIconThirstyId = "ThirstIconThirsty";
  27. [ValidatePrototypeId<SatiationIconPrototype>]
  28. private const string ThirstIconParchedId = "ThirstIconParched";
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<ThirstComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
  33. SubscribeLocalEvent<ThirstComponent, MapInitEvent>(OnMapInit);
  34. SubscribeLocalEvent<ThirstComponent, RejuvenateEvent>(OnRejuvenate);
  35. }
  36. private void OnMapInit(EntityUid uid, ThirstComponent component, MapInitEvent args)
  37. {
  38. // Do not change behavior unless starting value is explicitly defined
  39. if (component.CurrentThirst < 0)
  40. {
  41. component.CurrentThirst = _random.Next(
  42. (int) component.ThirstThresholds[ThirstThreshold.Thirsty] + 10,
  43. (int) component.ThirstThresholds[ThirstThreshold.Okay] - 1);
  44. }
  45. component.NextUpdateTime = _timing.CurTime;
  46. component.CurrentThirstThreshold = GetThirstThreshold(component, component.CurrentThirst);
  47. component.LastThirstThreshold = ThirstThreshold.Okay; // TODO: Potentially change this -> Used Okay because no effects.
  48. // TODO: Check all thresholds make sense and throw if they don't.
  49. UpdateEffects(uid, component);
  50. TryComp(uid, out MovementSpeedModifierComponent? moveMod);
  51. _movement.RefreshMovementSpeedModifiers(uid, moveMod);
  52. }
  53. private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args)
  54. {
  55. // TODO: This should really be taken care of somewhere else
  56. if (_jetpack.IsUserFlying(uid))
  57. return;
  58. var mod = component.CurrentThirstThreshold <= ThirstThreshold.Parched ? 0.75f : 1.0f;
  59. args.ModifySpeed(mod, mod);
  60. }
  61. private void OnRejuvenate(EntityUid uid, ThirstComponent component, RejuvenateEvent args)
  62. {
  63. SetThirst(uid, component, component.ThirstThresholds[ThirstThreshold.Okay]);
  64. }
  65. private ThirstThreshold GetThirstThreshold(ThirstComponent component, float amount)
  66. {
  67. ThirstThreshold result = ThirstThreshold.Dead;
  68. var value = component.ThirstThresholds[ThirstThreshold.OverHydrated];
  69. foreach (var threshold in component.ThirstThresholds)
  70. {
  71. if (threshold.Value <= value && threshold.Value >= amount)
  72. {
  73. result = threshold.Key;
  74. value = threshold.Value;
  75. }
  76. }
  77. return result;
  78. }
  79. public void ModifyThirst(EntityUid uid, ThirstComponent component, float amount)
  80. {
  81. SetThirst(uid, component, component.CurrentThirst + amount);
  82. }
  83. public void SetThirst(EntityUid uid, ThirstComponent component, float amount)
  84. {
  85. component.CurrentThirst = Math.Clamp(amount,
  86. component.ThirstThresholds[ThirstThreshold.Dead],
  87. component.ThirstThresholds[ThirstThreshold.OverHydrated]
  88. );
  89. EntityManager.DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst));
  90. }
  91. private bool IsMovementThreshold(ThirstThreshold threshold)
  92. {
  93. switch (threshold)
  94. {
  95. case ThirstThreshold.Dead:
  96. case ThirstThreshold.Parched:
  97. return true;
  98. case ThirstThreshold.Thirsty:
  99. case ThirstThreshold.Okay:
  100. case ThirstThreshold.OverHydrated:
  101. return false;
  102. default:
  103. throw new ArgumentOutOfRangeException(nameof(threshold), threshold, null);
  104. }
  105. }
  106. public bool TryGetStatusIconPrototype(ThirstComponent component, [NotNullWhen(true)] out SatiationIconPrototype? prototype)
  107. {
  108. switch (component.CurrentThirstThreshold)
  109. {
  110. case ThirstThreshold.OverHydrated:
  111. _prototype.TryIndex(ThirstIconOverhydratedId, out prototype);
  112. break;
  113. case ThirstThreshold.Thirsty:
  114. _prototype.TryIndex(ThirstIconThirstyId, out prototype);
  115. break;
  116. case ThirstThreshold.Parched:
  117. _prototype.TryIndex(ThirstIconParchedId, out prototype);
  118. break;
  119. default:
  120. prototype = null;
  121. break;
  122. }
  123. return prototype != null;
  124. }
  125. private void UpdateEffects(EntityUid uid, ThirstComponent component)
  126. {
  127. if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) &&
  128. TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent))
  129. {
  130. _movement.RefreshMovementSpeedModifiers(uid, movementSlowdownComponent);
  131. }
  132. // Update UI
  133. if (ThirstComponent.ThirstThresholdAlertTypes.TryGetValue(component.CurrentThirstThreshold, out var alertId))
  134. {
  135. _alerts.ShowAlert(uid, alertId);
  136. }
  137. else
  138. {
  139. _alerts.ClearAlertCategory(uid, component.ThirstyCategory);
  140. }
  141. switch (component.CurrentThirstThreshold)
  142. {
  143. case ThirstThreshold.OverHydrated:
  144. component.LastThirstThreshold = component.CurrentThirstThreshold;
  145. component.ActualDecayRate = component.BaseDecayRate * 1.2f;
  146. return;
  147. case ThirstThreshold.Okay:
  148. component.LastThirstThreshold = component.CurrentThirstThreshold;
  149. component.ActualDecayRate = component.BaseDecayRate;
  150. return;
  151. case ThirstThreshold.Thirsty:
  152. // Same as okay except with UI icon saying drink soon.
  153. component.LastThirstThreshold = component.CurrentThirstThreshold;
  154. component.ActualDecayRate = component.BaseDecayRate * 0.8f;
  155. return;
  156. case ThirstThreshold.Parched:
  157. _movement.RefreshMovementSpeedModifiers(uid);
  158. component.LastThirstThreshold = component.CurrentThirstThreshold;
  159. component.ActualDecayRate = component.BaseDecayRate * 0.6f;
  160. return;
  161. case ThirstThreshold.Dead:
  162. return;
  163. default:
  164. Log.Error($"No thirst threshold found for {component.CurrentThirstThreshold}");
  165. throw new ArgumentOutOfRangeException($"No thirst threshold found for {component.CurrentThirstThreshold}");
  166. }
  167. }
  168. public override void Update(float frameTime)
  169. {
  170. base.Update(frameTime);
  171. var query = EntityQueryEnumerator<ThirstComponent>();
  172. while (query.MoveNext(out var uid, out var thirst))
  173. {
  174. if (_timing.CurTime < thirst.NextUpdateTime)
  175. continue;
  176. thirst.NextUpdateTime += thirst.UpdateRate;
  177. ModifyThirst(uid, thirst, -thirst.ActualDecayRate);
  178. var calculatedThirstThreshold = GetThirstThreshold(thirst, thirst.CurrentThirst);
  179. if (calculatedThirstThreshold == thirst.CurrentThirstThreshold)
  180. continue;
  181. thirst.CurrentThirstThreshold = calculatedThirstThreshold;
  182. UpdateEffects(uid, thirst);
  183. }
  184. }
  185. }