DamageOverlay.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using Content.Shared.Mobs;
  2. using Robust.Client.Graphics;
  3. using Robust.Client.Player;
  4. using Robust.Shared.Enums;
  5. using Robust.Shared.Prototypes;
  6. using Robust.Shared.Timing;
  7. namespace Content.Client.UserInterface.Systems.DamageOverlays.Overlays;
  8. public sealed class DamageOverlay : Overlay
  9. {
  10. [Dependency] private readonly IGameTiming _timing = default!;
  11. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  12. [Dependency] private readonly IEntityManager _entityManager = default!;
  13. [Dependency] private readonly IPlayerManager _playerManager = default!;
  14. public override OverlaySpace Space => OverlaySpace.WorldSpace;
  15. private readonly ShaderInstance _critShader;
  16. private readonly ShaderInstance _oxygenShader;
  17. private readonly ShaderInstance _bruteShader;
  18. public MobState State = MobState.Alive;
  19. /// <summary>
  20. /// Handles the red pulsing overlay
  21. /// </summary>
  22. public float PainLevel = 0f;
  23. private float _oldPainLevel = 0f;
  24. /// <summary>
  25. /// Handles the darkening overlay.
  26. /// </summary>
  27. public float OxygenLevel = 0f;
  28. private float _oldOxygenLevel = 0f;
  29. /// <summary>
  30. /// Handles the white overlay when crit.
  31. /// </summary>
  32. public float CritLevel = 0f;
  33. private float _oldCritLevel = 0f;
  34. public float DeadLevel = 1f;
  35. public DamageOverlay()
  36. {
  37. // TODO: Replace
  38. IoCManager.InjectDependencies(this);
  39. _oxygenShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
  40. _critShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
  41. _bruteShader = _prototypeManager.Index<ShaderPrototype>("GradientCircleMask").InstanceUnique();
  42. }
  43. protected override void Draw(in OverlayDrawArgs args)
  44. {
  45. if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
  46. return;
  47. if (args.Viewport.Eye != eyeComp.Eye)
  48. return;
  49. /*
  50. * Here's the rundown:
  51. * 1. There's lerping for each level so the transitions are smooth.
  52. * 2. There's 3 overlays, 1 for brute damage, 1 for oxygen damage (that also doubles as a crit overlay),
  53. * and a white one during crit that closes in as you progress towards death. When you die it slowly disappears.
  54. * The crit overlay also occasionally reduces its alpha as a "blink"
  55. */
  56. var viewport = args.WorldAABB;
  57. var handle = args.WorldHandle;
  58. var distance = args.ViewportBounds.Width;
  59. var time = (float) _timing.RealTime.TotalSeconds;
  60. var lastFrameTime = (float) _timing.FrameTime.TotalSeconds;
  61. // If they just died then lerp out the white overlay.
  62. if (State != MobState.Dead)
  63. {
  64. DeadLevel = 1f;
  65. }
  66. else if (!MathHelper.CloseTo(0f, DeadLevel, 0.001f))
  67. {
  68. var diff = -DeadLevel;
  69. DeadLevel += GetDiff(diff, lastFrameTime);
  70. }
  71. else
  72. {
  73. DeadLevel = 0f;
  74. }
  75. if (!MathHelper.CloseTo(_oldPainLevel, PainLevel, 0.001f))
  76. {
  77. var diff = PainLevel - _oldPainLevel;
  78. _oldPainLevel += GetDiff(diff, lastFrameTime);
  79. }
  80. else
  81. {
  82. _oldPainLevel = PainLevel;
  83. }
  84. if (!MathHelper.CloseTo(_oldOxygenLevel, OxygenLevel, 0.001f))
  85. {
  86. var diff = OxygenLevel - _oldOxygenLevel;
  87. _oldOxygenLevel += GetDiff(diff, lastFrameTime);
  88. }
  89. else
  90. {
  91. _oldOxygenLevel = OxygenLevel;
  92. }
  93. if (!MathHelper.CloseTo(_oldCritLevel, CritLevel, 0.001f))
  94. {
  95. var diff = CritLevel - _oldCritLevel;
  96. _oldCritLevel += GetDiff(diff, lastFrameTime);
  97. }
  98. else
  99. {
  100. _oldCritLevel = CritLevel;
  101. }
  102. /*
  103. * darknessAlphaOuter is the maximum alpha for anything outside of the larger circle
  104. * darknessAlphaInner (on the shader) is the alpha for anything inside the smallest circle
  105. *
  106. * outerCircleRadius is what we end at for max level for the outer circle
  107. * outerCircleMaxRadius is what we start at for 0 level for the outer circle
  108. *
  109. * innerCircleRadius is what we end at for max level for the inner circle
  110. * innerCircleMaxRadius is what we start at for 0 level for the inner circle
  111. */
  112. // Makes debugging easier don't @ me
  113. float level = 0f;
  114. level = _oldPainLevel;
  115. // TODO: Lerping
  116. if (level > 0f && _oldCritLevel <= 0f)
  117. {
  118. var pulseRate = 3f;
  119. var adjustedTime = time * pulseRate;
  120. float outerMaxLevel = 2.0f * distance;
  121. float outerMinLevel = 0.8f * distance;
  122. float innerMaxLevel = 0.6f * distance;
  123. float innerMinLevel = 0.2f * distance;
  124. var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel);
  125. var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel);
  126. var pulse = MathF.Max(0f, MathF.Sin(adjustedTime));
  127. _bruteShader.SetParameter("time", pulse);
  128. _bruteShader.SetParameter("color", new Vector3(1f, 0f, 0f));
  129. _bruteShader.SetParameter("darknessAlphaOuter", 0.8f);
  130. _bruteShader.SetParameter("outerCircleRadius", outerRadius);
  131. _bruteShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance);
  132. _bruteShader.SetParameter("innerCircleRadius", innerRadius);
  133. _bruteShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.02f * distance);
  134. handle.UseShader(_bruteShader);
  135. handle.DrawRect(viewport, Color.White);
  136. }
  137. else
  138. {
  139. _oldPainLevel = PainLevel;
  140. }
  141. level = State != MobState.Critical ? _oldOxygenLevel : 1f;
  142. if (level > 0f)
  143. {
  144. float outerMaxLevel = 0.6f * distance;
  145. float outerMinLevel = 0.06f * distance;
  146. float innerMaxLevel = 0.02f * distance;
  147. float innerMinLevel = 0.02f * distance;
  148. var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel);
  149. var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel);
  150. float outerDarkness;
  151. float critTime;
  152. // If in crit then just fix it; also pulse it very occasionally so they can see more.
  153. if (_oldCritLevel > 0f)
  154. {
  155. var adjustedTime = time * 2f;
  156. critTime = MathF.Max(0, MathF.Sin(adjustedTime) + 2 * MathF.Sin(2 * adjustedTime / 4f) + MathF.Sin(adjustedTime / 4f) - 3f);
  157. if (critTime > 0f)
  158. {
  159. outerDarkness = 1f - critTime / 1.5f;
  160. }
  161. else
  162. {
  163. outerDarkness = 1f;
  164. }
  165. }
  166. else
  167. {
  168. outerDarkness = MathF.Min(0.98f, 0.3f * MathF.Log(level) + 1f);
  169. }
  170. _oxygenShader.SetParameter("time", 0.0f);
  171. _oxygenShader.SetParameter("color", new Vector3(0f, 0f, 0f));
  172. _oxygenShader.SetParameter("darknessAlphaOuter", outerDarkness);
  173. _oxygenShader.SetParameter("innerCircleRadius", innerRadius);
  174. _oxygenShader.SetParameter("innerCircleMaxRadius", innerRadius);
  175. _oxygenShader.SetParameter("outerCircleRadius", outerRadius);
  176. _oxygenShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance);
  177. handle.UseShader(_oxygenShader);
  178. handle.DrawRect(viewport, Color.White);
  179. }
  180. level = State != MobState.Dead ? _oldCritLevel : DeadLevel;
  181. if (level > 0f)
  182. {
  183. float outerMaxLevel = 2.0f * distance;
  184. float outerMinLevel = 1.0f * distance;
  185. float innerMaxLevel = 0.6f * distance;
  186. float innerMinLevel = 0.02f * distance;
  187. var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel);
  188. var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel);
  189. var pulse = MathF.Max(0f, MathF.Sin(time));
  190. // If in crit then just fix it; also pulse it very occasionally so they can see more.
  191. _critShader.SetParameter("time", pulse);
  192. _critShader.SetParameter("color", new Vector3(1f, 1f, 1f));
  193. _critShader.SetParameter("darknessAlphaOuter", 1.0f);
  194. _critShader.SetParameter("innerCircleRadius", innerRadius);
  195. _critShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.005f * distance);
  196. _critShader.SetParameter("outerCircleRadius", outerRadius);
  197. _critShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance);
  198. handle.UseShader(_critShader);
  199. handle.DrawRect(viewport, Color.White);
  200. }
  201. handle.UseShader(null);
  202. }
  203. private float GetDiff(float value, float lastFrameTime)
  204. {
  205. var adjustment = value * 5f * lastFrameTime;
  206. if (value < 0f)
  207. adjustment = Math.Clamp(adjustment, value, -value);
  208. else
  209. adjustment = Math.Clamp(adjustment, -value, value);
  210. return adjustment;
  211. }
  212. }