Files
RobustToolbox/Robust.Client/GameObjects/EntitySystems/EffectSystem.cs
Acruid 2183cd7ca1 Massive Namespace Cleanup (#1544)
* Removed the Interfaces folder.
* All objects inside the GameObjects subfolders are now in the GameObjects namespace.
* Added a Resharper DotSettings file to mark the GameObjects subfolders as not providing namespaces.
* Simplified Robust.client.Graphics namespace.
* Automated remove redundant using statements.
2021-02-10 23:27:19 -08:00

388 lines
15 KiB
C#

using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public class EffectSystem : EntitySystem
{
[Dependency] private readonly IGameTiming gameTiming = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IEyeManager eyeManager = default!;
[Dependency] private readonly IOverlayManager overlayManager = default!;
[Dependency] private readonly IPrototypeManager prototypeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly List<Effect> _Effects = new();
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<EffectSystemMessage>(CreateEffect);
SubscribeLocalEvent<EffectSystemMessage>(CreateEffect);
var overlay = new EffectOverlay(this, prototypeManager, _mapManager, _playerManager, _entityManager);
overlayManager.AddOverlay(overlay);
}
public override void Shutdown()
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
}
public void CreateEffect(EffectSystemMessage message)
{
// The source of effects is either local actions during FirstTimePredicted, or the network at LastServerTick
// When replaying predicted input, don't spam effects.
if(gameTiming.InPrediction && !gameTiming.IsFirstTimePredicted)
return;
if (message.AttachedEntityUid != null && message.Coordinates != default)
{
Logger.Warning("Set both an AttachedEntityUid and EntityCoordinates on an EffectSystemMessage for sprite {0} which is not supported!", message.EffectSprite);
}
if (message.LifeTime <= TimeSpan.Zero)
{
Logger.Warning("Effect using sprite {0} had zero lifetime.", message.EffectSprite);
return;
}
//Create effect from creation message
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
if (effect.AttachedEntityUid != null)
{
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
}
_Effects.Add(effect);
}
public override void FrameUpdate(float frameTime)
{
var curTime = gameTiming.CurTime;
for (int i = 0; i < _Effects.Count; i++)
{
var effect = _Effects[i];
//These effects have died
// Effects are purely visual, so they don't need to be ran through prediction.
// once CurTime ever passes DeathTime (clients render the top at IsFirstTimePredicted, where this happens) just remove them.
if (curTime > effect.Deathtime)
{
//Remove from the effects list and decrement the iterator
_Effects.Remove(effect);
i--;
}
else
{
//Update variables of the effect via its deltas
effect.Update(frameTime);
}
}
}
private class Effect
{
/// <summary>
/// Effect Sprite
/// This is the sprite that will be drawn as the "effect".
/// </summary>
public Texture EffectSprite { get; set; }
public RSI.State? RsiState { get; set; }
public int AnimationIndex { get; set; }
public float AnimationTime { get; set; }
public bool AnimationLoops { get; set; }
/// <summary>
/// Entity that the effect is attached to
/// </summary>
public IEntity? AttachedEntity { get; set; }
public EntityUid? AttachedEntityUid { get; }
/// <summary>
/// Offset relative to the attached entity
/// </summary>
public Vector2 AttachedOffset { get; }
/// <summary>
/// Effect position relative to the emit position
/// </summary>
public EntityCoordinates Coordinates;
/// <summary>
/// Where the emitter was when the effect was first emitted
/// </summary>
public EntityCoordinates EmitterCoordinates;
/// <summary>
/// Effect's x/y velocity
/// </summary>
public Vector2 Velocity = Vector2.Zero;
/// <summary>
/// Effect's x/y acceleration
/// </summary>
public Vector2 Acceleration = Vector2.Zero;
/// <summary>
/// Effect's radial velocity - relative to EmitterPosition
/// </summary>
public float RadialVelocity = 0f;
/// <summary>
/// Effect's radial acceleration
/// </summary>
public float RadialAcceleration = 0f;
/// <summary>
/// Effect's tangential velocity - relative to EmitterPosition
/// </summary>
public float TangentialVelocity = 0f;
/// <summary>
/// Effect's tangential acceleration
/// </summary>
public float TangentialAcceleration = 0f;
/// <summary>
/// Effect's spin about its center in radians
/// </summary>
public float Rotation = 0f;
/// <summary>
/// Rate of change of effect's spin
/// </summary>
public float RotationRate = 0f;
/// <summary>
/// Effect's current size
/// </summary>
public Vector2 Size = new(1f, 1f);
/// <summary>
/// Rate of change of effect's size change
/// </summary>
public float SizeDelta = 0f;
/// <summary>
/// Effect's current color
/// </summary>
public Vector4 Color = new(255, 255, 255, 255);
/// <summary>
/// Rate of change of effect's color
/// </summary>
public Vector4 ColorDelta = new(0, 0, 0, 0);
/// <summary>
/// True if the effect is affected by lighting.
/// </summary>
public bool Shaded = true;
/// <summary>
/// CurTime after which the effect will "die"
/// </summary>
public TimeSpan Deathtime;
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public Effect(EffectSystemMessage effectcreation, IResourceCache resourceCache, IMapManager mapManager, IEntityManager entityManager)
{
if (effectcreation.RsiState != null)
{
var rsi = resourceCache
.GetResource<RSIResource>(new ResourcePath("/Textures/") / effectcreation.EffectSprite)
.RSI;
RsiState = rsi[effectcreation.RsiState];
EffectSprite = RsiState.Frame0;
}
else
{
EffectSprite = resourceCache
.GetResource<TextureResource>(new ResourcePath("/Textures/") / effectcreation.EffectSprite)
.Texture;
}
AnimationLoops = effectcreation.AnimationLoops;
AttachedEntityUid = effectcreation.AttachedEntityUid;
AttachedOffset = effectcreation.AttachedOffset;
Coordinates = effectcreation.Coordinates;
EmitterCoordinates = effectcreation.EmitterCoordinates;
Velocity = effectcreation.Velocity;
Acceleration = effectcreation.Acceleration;
RadialVelocity = effectcreation.RadialVelocity;
RadialAcceleration = effectcreation.RadialAcceleration;
TangentialVelocity = effectcreation.TangentialVelocity;
TangentialAcceleration = effectcreation.TangentialAcceleration;
Rotation = effectcreation.Rotation;
RotationRate = effectcreation.RotationRate;
Size = effectcreation.Size;
SizeDelta = effectcreation.SizeDelta;
Color = effectcreation.Color;
ColorDelta = effectcreation.ColorDelta;
Shaded = effectcreation.Shaded;
_mapManager = mapManager;
_entityManager = entityManager;
}
public void Update(float frameTime)
{
Velocity += Acceleration * frameTime;
RadialVelocity += RadialAcceleration * frameTime;
TangentialVelocity += TangentialAcceleration * frameTime;
var deltaPosition = new Vector2(0f, 0f);
//If we have an emitter we can do special effects around that emitter position
if (_mapManager.GridExists(EmitterCoordinates.GetGridId(_entityManager)))
{
//Calculate delta p due to radial velocity
var positionRelativeToEmitter =
Coordinates.ToMapPos(_entityManager) - EmitterCoordinates.ToMapPos(_entityManager);
var deltaRadial = RadialVelocity * frameTime;
deltaPosition = positionRelativeToEmitter * (deltaRadial / positionRelativeToEmitter.Length);
//Calculate delta p due to tangential velocity
var radius = positionRelativeToEmitter.Length;
if (radius > 0)
{
var theta = (float) Math.Atan2(positionRelativeToEmitter.Y, positionRelativeToEmitter.X);
theta += TangentialVelocity * frameTime;
deltaPosition += new Vector2(radius * (float) Math.Cos(theta), radius * (float) Math.Sin(theta))
- positionRelativeToEmitter;
}
}
//Calculate new position from our velocity as well as possible rotation/movement around emitter
deltaPosition += Velocity * frameTime;
Coordinates = Coordinates.Offset(deltaPosition);
//Finish calculating new rotation, size, color
Rotation += RotationRate * frameTime;
Size += SizeDelta * frameTime;
Color += ColorDelta * frameTime;
if (RsiState == null)
{
return;
}
// Calculate RSI animations.
var delayCount = RsiState.DelayCount;
if (delayCount > 0 && (AnimationLoops || AnimationIndex < delayCount - 1))
{
AnimationTime += frameTime;
while (RsiState.GetDelay(AnimationIndex) < AnimationTime)
{
var delay = RsiState.GetDelay(AnimationIndex);
AnimationIndex += 1;
AnimationTime -= delay;
if (AnimationIndex == delayCount)
{
if (AnimationLoops)
{
AnimationIndex = 0;
}
else
{
break;
}
}
EffectSprite = RsiState.GetFrame(RSI.State.Direction.South, AnimationIndex);
}
}
}
}
private static Color ToColor(Vector4 color)
{
color = Vector4.Clamp(color / 255f, Vector4.Zero, Vector4.One);
return new Color(color.X, color.Y, color.Z, color.W);
}
private sealed class EffectOverlay : Overlay
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
private readonly EffectSystem _owner;
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
_mapManager = mapMan;
_playerManager = playerMan;
_entityManager = entityManager;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
var map = _owner.eyeManager.CurrentMap;
var worldHandle = (DrawingHandleWorld) handle;
ShaderInstance? currentShader = null;
var player = _playerManager.LocalPlayer?.ControlledEntity;
foreach (var effect in _owner._Effects)
{
if (effect.AttachedEntity?.Transform.MapID != player?.Transform.MapID &&
effect.Coordinates.GetMapId(_entityManager) != map)
{
continue;
}
var newShader = effect.Shaded ? null : _unshadedShader;
if (newShader != currentShader)
{
worldHandle.UseShader(newShader);
currentShader = newShader;
}
var effectSprite = effect.EffectSprite;
var effectOrigin = effect.AttachedEntity?.Transform.MapPosition.Position + effect.AttachedOffset ??
effect.Coordinates.ToMapPos(_entityManager);
var effectArea = Box2.CenteredAround(effectOrigin, effect.Size);
var rotatedBox = new Box2Rotated(effectArea, effect.Rotation, effectOrigin);
worldHandle.DrawTextureRect(effectSprite, rotatedBox, ToColor(effect.Color));
}
}
}
}
}