#nullable enable using System; using System.Collections.Generic; using System.Linq; using Content.Client.GameObjects.Components.StationEvents; using Content.Shared.GameObjects.Components.Mobs; using JetBrains.Annotations; using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Player; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Client.StationEvents { [UsedImplicitly] public sealed class RadiationPulseOverlay : Overlay { [Dependency] private readonly IComponentManager _componentManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; /// /// Current color of a pulse /// private readonly Dictionary _colors = new(); /// /// Whether our alpha is increasing or decreasing and at what time does it flip (or stop) /// private readonly Dictionary _transitions = new(); /// /// How much the alpha changes per second for each pulse /// private readonly Dictionary _alphaRateOfChange = new(); private TimeSpan _lastTick; // TODO: When worldHandle can do DrawCircle change this. public override OverlaySpace Space => OverlaySpace.ScreenSpace; public RadiationPulseOverlay() : base(nameof(RadiationPulseOverlay)) { IoCManager.InjectDependencies(this); _lastTick = _gameTiming.CurTime; } /// /// Get the current color for the entity, /// accounting for what its alpha should be and whether it should be transitioning in or out /// /// /// frametime /// /// private Color GetColor(IEntity entity, float elapsedTime, TimeSpan endTime) { var currentTime = _gameTiming.CurTime; // New pulse if (!_colors.ContainsKey(entity)) { UpdateTransition(entity, currentTime, endTime); } var currentColor = _colors[entity]; var alphaChange = _alphaRateOfChange[entity] * elapsedTime; if (!_transitions[entity].EasingIn) { alphaChange *= -1; } if (currentTime > _transitions[entity].TransitionTime) { UpdateTransition(entity, currentTime, endTime); } _colors[entity] = _colors[entity].WithAlpha(currentColor.A + alphaChange); return _colors[entity]; } private void UpdateTransition(IEntity entity, TimeSpan currentTime, TimeSpan endTime) { bool easingIn; TimeSpan transitionTime; if (!_transitions.TryGetValue(entity, out var transition)) { // Start as false because it will immediately be flipped easingIn = false; transitionTime = (endTime - currentTime) / 2 + currentTime; } else { easingIn = transition.EasingIn; transitionTime = endTime; } _transitions[entity] = (!easingIn, transitionTime); _colors[entity] = Color.Green.WithAlpha(0.0f); _alphaRateOfChange[entity] = 1.0f / (float) (transitionTime - currentTime).TotalSeconds; } protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { // PVS should control the overlay pretty well so the overlay doesn't get instantiated unless we're near one... var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; if (playerEntity == null) { return; } var elapsedTime = (float) (_gameTiming.CurTime - _lastTick).TotalSeconds; _lastTick = _gameTiming.CurTime; var radiationPulses = _componentManager .EntityQuery() .ToList(); var screenHandle = (DrawingHandleScreen) handle; var viewport = _eyeManager.GetWorldViewport(); foreach (var grid in _mapManager.FindGridsIntersecting(playerEntity.Transform.MapID, viewport)) { foreach (var pulse in radiationPulses) { if (!pulse.Draw || grid.Index != pulse.Owner.Transform.GridID) continue; // TODO: Check if viewport intersects circle var circlePosition = _eyeManager.WorldToScreen(pulse.Owner.Transform.WorldPosition); // change to worldhandle when implemented screenHandle.DrawCircle( circlePosition, pulse.Range * 64, GetColor(pulse.Owner, pulse.Decay ? elapsedTime : 0, pulse.EndTime)); } } } } }