RadiationPulseOverlay.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. using System.Numerics;
  2. using Content.Shared.Radiation.Components;
  3. using Robust.Client.GameObjects;
  4. using Robust.Client.Graphics;
  5. using Robust.Shared.Enums;
  6. using Robust.Shared.Graphics;
  7. using Robust.Shared.Map;
  8. using Robust.Shared.Physics;
  9. using Robust.Shared.Prototypes;
  10. using Robust.Shared.Timing;
  11. namespace Content.Client.Radiation.Overlays
  12. {
  13. public sealed class RadiationPulseOverlay : Overlay
  14. {
  15. [Dependency] private readonly IEntityManager _entityManager = default!;
  16. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  17. [Dependency] private readonly IGameTiming _gameTiming = default!;
  18. private TransformSystem? _transform;
  19. private const float MaxDist = 15.0f;
  20. public override OverlaySpace Space => OverlaySpace.WorldSpace;
  21. public override bool RequestScreenTexture => true;
  22. private readonly ShaderInstance _baseShader;
  23. private readonly Dictionary<EntityUid, (ShaderInstance shd, RadiationShaderInstance instance)> _pulses = new();
  24. public RadiationPulseOverlay()
  25. {
  26. IoCManager.InjectDependencies(this);
  27. _baseShader = _prototypeManager.Index<ShaderPrototype>("Radiation").Instance().Duplicate();
  28. }
  29. protected override bool BeforeDraw(in OverlayDrawArgs args)
  30. {
  31. RadiationQuery(args.Viewport.Eye);
  32. return _pulses.Count > 0;
  33. }
  34. protected override void Draw(in OverlayDrawArgs args)
  35. {
  36. if (ScreenTexture == null)
  37. return;
  38. var worldHandle = args.WorldHandle;
  39. var viewport = args.Viewport;
  40. foreach ((var shd, var instance) in _pulses.Values)
  41. {
  42. if (instance.CurrentMapCoords.MapId != args.MapId)
  43. continue;
  44. // To be clear, this needs to use "inside-viewport" pixels.
  45. // In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates).
  46. var tempCoords = viewport.WorldToLocal(instance.CurrentMapCoords.Position);
  47. tempCoords.Y = viewport.Size.Y - tempCoords.Y;
  48. shd?.SetParameter("renderScale", viewport.RenderScale);
  49. shd?.SetParameter("positionInput", tempCoords);
  50. shd?.SetParameter("range", instance.Range);
  51. var life = (_gameTiming.RealTime - instance.Start).TotalSeconds / instance.Duration;
  52. shd?.SetParameter("life", (float) life);
  53. // There's probably a very good reason not to do this.
  54. // Oh well!
  55. shd?.SetParameter("SCREEN_TEXTURE", viewport.RenderTarget.Texture);
  56. worldHandle.UseShader(shd);
  57. worldHandle.DrawRect(Box2.CenteredAround(instance.CurrentMapCoords.Position, new Vector2(instance.Range, instance.Range) * 2f), Color.White);
  58. }
  59. worldHandle.UseShader(null);
  60. }
  61. //Queries all pulses on the map and either adds or removes them from the list of rendered pulses based on whether they should be drawn (in range? on the same z-level/map? pulse entity still exists?)
  62. private void RadiationQuery(IEye? currentEye)
  63. {
  64. _transform ??= _entityManager.System<TransformSystem>();
  65. if (currentEye == null)
  66. {
  67. _pulses.Clear();
  68. return;
  69. }
  70. var currentEyeLoc = currentEye.Position;
  71. var pulses = _entityManager.EntityQueryEnumerator<RadiationPulseComponent>();
  72. //Add all pulses that are not added yet but qualify
  73. while (pulses.MoveNext(out var pulseEntity, out var pulse))
  74. {
  75. if (!_pulses.ContainsKey(pulseEntity) && PulseQualifies(pulseEntity, currentEyeLoc))
  76. {
  77. _pulses.Add(
  78. pulseEntity,
  79. (
  80. _baseShader.Duplicate(),
  81. new RadiationShaderInstance(
  82. _transform.GetMapCoordinates(pulseEntity),
  83. pulse.VisualRange,
  84. pulse.StartTime,
  85. pulse.VisualDuration
  86. )
  87. )
  88. );
  89. }
  90. }
  91. var activeShaderIds = _pulses.Keys;
  92. foreach (var pulseEntity in activeShaderIds) //Remove all pulses that are added and no longer qualify
  93. {
  94. if (_entityManager.EntityExists(pulseEntity) &&
  95. PulseQualifies(pulseEntity, currentEyeLoc) &&
  96. _entityManager.TryGetComponent(pulseEntity, out RadiationPulseComponent? pulse))
  97. {
  98. var shaderInstance = _pulses[pulseEntity];
  99. shaderInstance.instance.CurrentMapCoords = _transform.GetMapCoordinates(pulseEntity);
  100. shaderInstance.instance.Range = pulse.VisualRange;
  101. }
  102. else
  103. {
  104. _pulses[pulseEntity].shd.Dispose();
  105. _pulses.Remove(pulseEntity);
  106. }
  107. }
  108. }
  109. private bool PulseQualifies(EntityUid pulseEntity, MapCoordinates currentEyeLoc)
  110. {
  111. var transformComponent = _entityManager.GetComponent<TransformComponent>(pulseEntity);
  112. var transformSystem = _entityManager.System<SharedTransformSystem>();
  113. return transformComponent.MapID == currentEyeLoc.MapId
  114. && transformSystem.InRange(transformComponent.Coordinates, transformSystem.ToCoordinates(transformComponent.ParentUid, currentEyeLoc), MaxDist);
  115. }
  116. private sealed record RadiationShaderInstance(MapCoordinates CurrentMapCoords, float Range, TimeSpan Start, float Duration)
  117. {
  118. public MapCoordinates CurrentMapCoords = CurrentMapCoords;
  119. public float Range = Range;
  120. public TimeSpan Start = Start;
  121. public float Duration = Duration;
  122. };
  123. }
  124. }