mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Refactor: Gas tile overlay split (#42881)
* yeah * split2 * well * cleanup * docs fix * Dependency cleanup * Update Content.Client/Atmos/EntitySystems/GasTileFireOverlaySystem.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Update Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Update Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * yup * modifying a lot anyways * other cleanup --------- Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
This commit is contained in:
parent
05b6ec1862
commit
d5ed6aa462
7 changed files with 559 additions and 386 deletions
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Client.Atmos.Overlays;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System responsible for rendering atmos fire animations using <see cref="GasTileFireOverlay"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileFireOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private GasTileFireOverlay _fireOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_fireOverlay = new GasTileFireOverlay();
|
||||
_overlayMan.AddOverlay(_fireOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileFireOverlay>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +1,85 @@
|
|||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSys = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private GasTileOverlay _overlay = default!;
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
public override void Initialize()
|
||||
switch (args.Current)
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileOverlay>();
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
switch (args.Current)
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
{
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
|
||||
{
|
||||
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var index in removedIndicies)
|
||||
{
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
|
||||
foreach (var (nent, gridData) in ev.UpdatedChunks)
|
||||
{
|
||||
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var chunkData in gridData)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var index in removedIndicies)
|
||||
{
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (nent, gridData) in ev.UpdatedChunks)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var chunkData in gridData)
|
||||
{
|
||||
comp.Chunks[chunkData.Index] = chunkData;
|
||||
}
|
||||
comp.Chunks[chunkData.Index] = chunkData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
using Content.Client.Atmos.Overlays;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System responsible for rendering visible atmos gasses (like plasma for example) using <see cref="GasTileVisibleGasOverlay"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileVisibleGasOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private GasTileVisibleGasOverlay _visibleGasOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_visibleGasOverlay = new GasTileVisibleGasOverlay();
|
||||
_overlayMan.AddOverlay(_visibleGasOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileVisibleGasOverlay>();
|
||||
}
|
||||
|
||||
}
|
||||
172
Content.Client/Atmos/Overlays/GasTileFireOverlay.cs
Normal file
172
Content.Client/Atmos/Overlays/GasTileFireOverlay.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Species;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay responsible for rendering atmos fire animation.
|
||||
/// </summary>
|
||||
public sealed class GasTileFireOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private readonly SharedMapSystem _mapSystem = default!;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
private const int FireStates = 3;
|
||||
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
|
||||
|
||||
public const int GasOverlayZIndex = (int)Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
|
||||
|
||||
public GasTileFireOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_shader = _protoMan.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_timer = new float[FireStates];
|
||||
_frameDelays = new float[FireStates][];
|
||||
_frameCounter = new int[FireStates];
|
||||
_frames = new Texture[FireStates][];
|
||||
|
||||
var fire = _resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
if (!fire.TryGetState((i + 1).ToString(), out var state))
|
||||
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time) continue;
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(state.shader);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.FireState == 0)
|
||||
continue;
|
||||
|
||||
var index = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(index))
|
||||
continue;
|
||||
|
||||
var fireState = gas.FireState - 1;
|
||||
var texture = state.frames[fireState][state.frameCounter[fireState]];
|
||||
state.drawHandle.DrawTexture(texture, index);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Atmos.Components;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
public sealed class GasTileOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedAtmosphereSystem _atmosphereSystem;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
// Gas overlays
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
// Fire overlays
|
||||
private const int FireStates = 3;
|
||||
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
|
||||
|
||||
private readonly float[] _fireTimer = new float[FireStates];
|
||||
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
||||
private readonly int[] _fireFrameCounter = new int[FireStates];
|
||||
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
||||
|
||||
private int _gasCount;
|
||||
|
||||
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
|
||||
|
||||
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||
_atmosphereSystem = entManager.System<SharedAtmosphereSystem>();
|
||||
_mapSystem = entManager.System<SharedMapSystem>();
|
||||
_xformSys = xformSys;
|
||||
_shader = protoMan.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_gasCount = system.VisibleGasId.Length;
|
||||
_timer = new float[_gasCount];
|
||||
_frameDelays = new float[_gasCount][];
|
||||
_frameCounter = new int[_gasCount];
|
||||
_frames = new Texture[_gasCount][];
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var gasPrototype = _atmosphereSystem.GetGas(system.VisibleGasId[i]);
|
||||
|
||||
SpriteSpecifier overlay;
|
||||
|
||||
if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
|
||||
overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
|
||||
else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
|
||||
overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture));
|
||||
else
|
||||
continue;
|
||||
|
||||
switch (overlay)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if (!rsi.TryGetState(stateId, out var state))
|
||||
continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
_frames[i] = new[] { spriteSys.Frame0(texture) };
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var fire = resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
if (!fire.TryGetState((i + 1).ToString(), out var state))
|
||||
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
|
||||
|
||||
_fireFrames[i] = state.GetFrames(RsiDirection.South);
|
||||
_fireFrameDelays[i] = state.GetDelays();
|
||||
_fireFrameCounter[i] = 0;
|
||||
}
|
||||
}
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time)
|
||||
continue;
|
||||
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _fireFrameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _fireFrameCounter[i];
|
||||
_fireTimer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_fireTimer[i] < time) continue;
|
||||
_fireTimer[i] -= time;
|
||||
_fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_gasCount,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_fireFrames,
|
||||
_fireFrameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
|
||||
DrawMapOverlay(drawHandle, args, mapUid, atmos);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
int gasCount,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
Texture[][] fireFrames,
|
||||
int[] fireFrameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int) MathF.Floor(floatBounds.Left),
|
||||
(int) MathF.Floor(floatBounds.Bottom),
|
||||
(int) MathF.Ceiling(floatBounds.Right),
|
||||
(int) MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(null);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.Opacity == null!)
|
||||
continue;
|
||||
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < state.gasCount; i++)
|
||||
{
|
||||
var opacity = gas.Opacity[i];
|
||||
if (opacity > 0)
|
||||
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And again for fire, with the unshaded shader
|
||||
state.drawHandle.UseShader(state.shader);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.FireState == 0)
|
||||
continue;
|
||||
|
||||
var index = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(index))
|
||||
continue;
|
||||
|
||||
var fireState = gas.FireState - 1;
|
||||
var texture = state.fireFrames[fireState][state.fireFrameCounter[fireState]];
|
||||
state.drawHandle.DrawTexture(texture, index);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawMapOverlay(
|
||||
DrawingHandleWorld handle,
|
||||
OverlayDrawArgs args,
|
||||
EntityUid map,
|
||||
MapAtmosphereComponent atmos)
|
||||
{
|
||||
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
|
||||
|
||||
// map-grid atmospheres get drawn above grids
|
||||
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// Normal map atmospheres get drawn below grids
|
||||
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
|
||||
return;
|
||||
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
258
Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs
Normal file
258
Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
using Content.Client.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay responsible for rendering visible atmos gasses (like plasma for example) usin.
|
||||
/// </summary>
|
||||
public sealed class GasTileVisibleGasOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly SharedAtmosphereSystem _atmosphereSystem;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private readonly SharedGasTileOverlaySystem _gasTileOverlaySystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
// Gas overlays
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
private readonly int _gasCount;
|
||||
|
||||
public const int GasOverlayZIndex = (int)DrawDepth.Gasses; // Under ghosts and fire, above mostly everything else
|
||||
|
||||
public GasTileVisibleGasOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_atmosphereSystem = _entManager.System<SharedAtmosphereSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_gasTileOverlaySystem = _entManager.System<SharedGasTileOverlaySystem>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
_shader = _protoManager.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_gasCount = _gasTileOverlaySystem.VisibleGasId.Length;
|
||||
_timer = new float[_gasCount];
|
||||
_frameDelays = new float[_gasCount][];
|
||||
_frameCounter = new int[_gasCount];
|
||||
_frames = new Texture[_gasCount][];
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var gasPrototype = _atmosphereSystem.GetGas(_gasTileOverlaySystem.VisibleGasId[i]);
|
||||
|
||||
SpriteSpecifier overlay;
|
||||
|
||||
if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) &&
|
||||
!string.IsNullOrEmpty(gasPrototype.GasOverlayState))
|
||||
overlay = new SpriteSpecifier.Rsi(new(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
|
||||
else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
|
||||
overlay = new SpriteSpecifier.Texture(new(gasPrototype.GasOverlayTexture));
|
||||
else
|
||||
continue;
|
||||
|
||||
switch (overlay)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if (!rsi.TryGetState(stateId, out var state))
|
||||
continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
_frames[i] = new[] { _spriteSystem.Frame0(texture) };
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time)
|
||||
continue;
|
||||
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_gasCount,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
|
||||
DrawMapOverlay(drawHandle, args, mapUid, atmos);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId,
|
||||
args.WorldAABB,
|
||||
ref gridState,
|
||||
static (EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
int gasCount,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(null);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.Opacity == null!)
|
||||
continue;
|
||||
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < state.gasCount; i++)
|
||||
{
|
||||
var opacity = gas.Opacity[i];
|
||||
if (opacity > 0)
|
||||
{
|
||||
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]],
|
||||
tilePosition,
|
||||
Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawMapOverlay(
|
||||
DrawingHandleWorld handle,
|
||||
OverlayDrawArgs args,
|
||||
EntityUid map,
|
||||
MapAtmosphereComponent atmos)
|
||||
{
|
||||
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
|
||||
|
||||
// map-grid atmospheres get drawn above grids
|
||||
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// Normal map atmospheres get drawn below grids
|
||||
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
|
||||
return;
|
||||
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,17 +112,22 @@ namespace Content.Shared.DrawDepth
|
|||
/// </summary>
|
||||
Overdoors = DrawDepthTag.Default + 10,
|
||||
|
||||
/// <summary>
|
||||
/// Visible atmos gas.
|
||||
/// </summary>
|
||||
Gasses = DrawDepthTag.Default + 11,
|
||||
|
||||
/// <summary>
|
||||
/// Explosions, fire, melee swings. Whatever.
|
||||
/// </summary>
|
||||
Effects = DrawDepthTag.Default + 11,
|
||||
Effects = DrawDepthTag.Default + 12,
|
||||
|
||||
Ghosts = DrawDepthTag.Default + 12,
|
||||
Ghosts = DrawDepthTag.Default + 13,
|
||||
|
||||
/// <summary>
|
||||
/// Use this selectively if it absolutely needs to be drawn above (almost) everything else. Examples include
|
||||
/// the pointing arrow, the drag & drop ghost-entity, and some debug tools.
|
||||
/// </summary>
|
||||
Overlays = DrawDepthTag.Default + 13,
|
||||
Overlays = DrawDepthTag.Default + 14,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue