Files
tbd-station-14/Content.Client/StationEvents/RadiationPulseOverlay.cs
metalgearsloth 5962280d36 Station events (#1518)
* Station event system

Adds 2 basic events: (Power) GridCheck and RadiationStorm (based on the goonstation version).
The system itself to choose events is based on tgstation's implementation.
This also adds the event command that can be run to force specific events.

There's still some other TODO items for these to be complete, to my knowledge:
1. There's no worldspace DrawCircle method (though the radstorm could look a lot nicer with a shader).
2. The PlayGlobal power_off / power_on audio seems to cut out halfway-through
3. (I think this is a known issue) lights still emit light until you get closer in a gridcheck so PVS range might need bumping.

* Invariants for event names

* Fix random event shutdown

* Mix stereo announcements to mono

* Address feedback

* Remove redundant client system and use the overlay component instead
* Drop the server prefix

* Fix radiation overlay enum

* use entityquery instead

* zum's feedback

* Use EntityQuery

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2020-08-13 22:52:17 +02:00

152 lines
5.8 KiB
C#

#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 Color = Robust.Shared.Maths.Color;
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!;
/// <summary>
/// Current color of a pulse
/// </summary>
private readonly Dictionary<IEntity, Color> _colors = new Dictionary<IEntity, Color>();
/// <summary>
/// Whether our alpha is increasing or decreasing and at what time does it flip (or stop)
/// </summary>
private readonly Dictionary<IEntity, (bool EasingIn, TimeSpan TransitionTime)> _transitions =
new Dictionary<IEntity, (bool EasingIn, TimeSpan TransitionTime)>();
/// <summary>
/// How much the alpha changes per second for each pulse
/// </summary>
private readonly Dictionary<IEntity, float> _alphaRateOfChange = new Dictionary<IEntity, float>();
private TimeSpan _lastTick;
// TODO: When worldHandle can do DrawCircle change this.
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public RadiationPulseOverlay() : base(nameof(SharedOverlayID.RadiationPulseOverlay))
{
IoCManager.InjectDependencies(this);
_lastTick = _gameTiming.CurTime;
}
/// <summary>
/// Get the current color for the entity,
/// accounting for what its alpha should be and whether it should be transitioning in or out
/// </summary>
/// <param name="entity"></param>
/// <param name="elapsedTime">frametime</param>
/// <param name="endTime"></param>
/// <returns></returns>
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<RadiationPulseComponent>()
.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 (grid.Index != pulse.Owner.Transform.GridID) continue;
// TODO: Check if viewport intersects circle
var circlePosition = _eyeManager.WorldToScreen(pulse.Owner.Transform.WorldPosition);
var comp = (RadiationPulseComponent) pulse;
// change to worldhandle when implemented
screenHandle.DrawCircle(
circlePosition,
comp.Range * 64,
GetColor(pulse.Owner, elapsedTime, comp.EndTime));
}
}
}
}
}