Explosion refactor TEST MERG (#6995)
* Explosions * fix yaml typo and prevent silly UI inputs * oop Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Administration.UI.SpawnExplosion;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ExplosionDebugOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public Dictionary<int, List<Vector2i>>? SpaceTiles;
|
||||
public Dictionary<GridId, Dictionary<int, List<Vector2i>>> Tiles = new();
|
||||
public List<float> Intensity = new();
|
||||
public float TotalIntensity;
|
||||
public float Slope;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
public Matrix3 SpaceMatrix;
|
||||
public MapId Map;
|
||||
|
||||
private readonly Font _font;
|
||||
|
||||
public ExplosionDebugOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Map != _eyeManager.CurrentMap)
|
||||
return;
|
||||
|
||||
if (Tiles.Count == 0 && SpaceTiles == null)
|
||||
return;
|
||||
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawScreen(OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.ScreenHandle;
|
||||
Box2 gridBounds;
|
||||
|
||||
foreach (var (gridId, tileSets) in Tiles)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
continue;
|
||||
|
||||
var gridXform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
|
||||
gridBounds = gridXform.InvWorldMatrix.TransformBox(args.WorldBounds);
|
||||
|
||||
DrawText(handle, gridBounds, gridXform.WorldMatrix, tileSets);
|
||||
}
|
||||
|
||||
if (SpaceTiles == null)
|
||||
return;
|
||||
|
||||
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
|
||||
|
||||
DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles);
|
||||
}
|
||||
|
||||
private void DrawText(
|
||||
DrawingHandleScreen handle,
|
||||
Box2 gridBounds,
|
||||
Matrix3 transform,
|
||||
Dictionary<int, List<Vector2i>> tileSets)
|
||||
{
|
||||
for (var i = 1; i < Intensity.Count; i++)
|
||||
{
|
||||
if (!tileSets.TryGetValue(i, out var tiles))
|
||||
continue;
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
// is the center of this tile visible to the user?
|
||||
if (!gridBounds.Contains((Vector2) tile + 0.5f))
|
||||
continue;
|
||||
|
||||
var worldCenter = transform.Transform((Vector2) tile + 0.5f);
|
||||
|
||||
var screenCenter = _eyeManager.WorldToScreen(worldCenter);
|
||||
|
||||
if (Intensity![i] > 9)
|
||||
screenCenter += (-12, -8);
|
||||
else
|
||||
screenCenter += (-8, -8);
|
||||
|
||||
handle.DrawString(_font, screenCenter, Intensity![i].ToString("F2"));
|
||||
}
|
||||
}
|
||||
|
||||
if (tileSets.ContainsKey(0))
|
||||
{
|
||||
var epicenter = tileSets[0].First();
|
||||
var worldCenter = transform.Transform((Vector2) epicenter + 0.5f);
|
||||
var screenCenter = _eyeManager.WorldToScreen(worldCenter) + (-24, -24);
|
||||
var text = $"{Intensity![0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}";
|
||||
handle.DrawString(_font, screenCenter, text);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
Box2 gridBounds;
|
||||
|
||||
foreach (var (gridId, tileSets) in Tiles)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
continue;
|
||||
|
||||
var gridXform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
|
||||
handle.SetTransform(gridXform.WorldMatrix);
|
||||
gridBounds = gridXform.InvWorldMatrix.TransformBox(args.WorldBounds);
|
||||
|
||||
DrawTiles(handle, gridBounds, tileSets);
|
||||
}
|
||||
|
||||
if (SpaceTiles == null)
|
||||
return;
|
||||
|
||||
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
|
||||
handle.SetTransform(SpaceMatrix);
|
||||
|
||||
DrawTiles(handle, gridBounds, SpaceTiles);
|
||||
}
|
||||
|
||||
private void DrawTiles(
|
||||
DrawingHandleWorld handle,
|
||||
Box2 gridBounds,
|
||||
Dictionary<int, List<Vector2i>> tileSets)
|
||||
{
|
||||
for (var i = 0; i < Intensity.Count; i++)
|
||||
{
|
||||
var color = ColorMap(Intensity![i]);
|
||||
var colorTransparent = color;
|
||||
colorTransparent.A = 0.2f;
|
||||
|
||||
|
||||
if (!tileSets.TryGetValue(i, out var tiles))
|
||||
continue;
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
// is the center of this tile visible to the user?
|
||||
if (!gridBounds.Contains((Vector2) tile + 0.5f))
|
||||
continue;
|
||||
|
||||
var box = Box2.UnitCentered.Translated((Vector2) tile + 0.5f);
|
||||
handle.DrawRect(box, color, false);
|
||||
handle.DrawRect(box, colorTransparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Color ColorMap(float intensity)
|
||||
{
|
||||
var frac = 1 - intensity / Intensity![0];
|
||||
Color result;
|
||||
if (frac < 0.5f)
|
||||
result = Color.InterpolateBetween(Color.Red, Color.Orange, frac * 2);
|
||||
else
|
||||
result = Color.InterpolateBetween(Color.Orange, Color.Yellow, (frac - 0.5f) * 2);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Administration.UI.SpawnExplosion;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SpawnExplosionEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
|
||||
private readonly SpawnExplosionWindow _window;
|
||||
private ExplosionDebugOverlay? _debugOverlay;
|
||||
|
||||
public SpawnExplosionEui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_window = new SpawnExplosionWindow(this);
|
||||
_window.OnClose += () => SendMessage(new SpawnExplosionEuiMsg.Close());
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
_window.OnClose -= () => SendMessage(new SpawnExplosionEuiMsg.Close());
|
||||
_window.Close();
|
||||
ClearOverlay();
|
||||
}
|
||||
|
||||
public void ClearOverlay()
|
||||
{
|
||||
if (_overlayManager.HasOverlay<ExplosionDebugOverlay>())
|
||||
_overlayManager.RemoveOverlay<ExplosionDebugOverlay>();
|
||||
_debugOverlay = null;
|
||||
}
|
||||
|
||||
public void RequestPreviewData(MapCoordinates epicenter, string typeId, float totalIntensity, float intensitySlope, float maxIntensity)
|
||||
{
|
||||
var msg = new SpawnExplosionEuiMsg.PreviewRequest(epicenter, typeId, totalIntensity, intensitySlope, maxIntensity);
|
||||
SendMessage(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receive explosion preview data and add a client-side explosion preview overlay
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
if (msg is not SpawnExplosionEuiMsg.PreviewData data)
|
||||
return;
|
||||
|
||||
if (_debugOverlay == null)
|
||||
{
|
||||
_debugOverlay = new();
|
||||
_overlayManager.AddOverlay(_debugOverlay);
|
||||
}
|
||||
|
||||
_debugOverlay.Tiles = data.Explosion.Tiles;
|
||||
_debugOverlay.SpaceTiles = data.Explosion.SpaceTiles;
|
||||
_debugOverlay.Intensity = data.Explosion.Intensity;
|
||||
_debugOverlay.Slope = data.Slope;
|
||||
_debugOverlay.TotalIntensity = data.TotalIntensity;
|
||||
_debugOverlay.Map = data.Explosion.Epicenter.MapId;
|
||||
_debugOverlay.SpaceMatrix = data.Explosion.SpaceMatrix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'admin-explosion-eui-title'}"
|
||||
SetHeight="380">
|
||||
<BoxContainer Name="MainContainer" Orientation="Vertical">
|
||||
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-type'}" MinSize="120 0" />
|
||||
<OptionButton Name="ExplosionOption" MinSize="70 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-mapid'}" MinSize="120 0" />
|
||||
<OptionButton Name="MapOptions" MinSize="70 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-xmap'}" MinSize="120 0" />
|
||||
<FloatSpinBox Name="MapX" MinSize="70 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-ymap'}" MinSize="120 0" />
|
||||
<FloatSpinBox Name="MapY" MinSize="70 0" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<Button Name="Recentre" Text="{Loc 'admin-explosion-eui-label-current'}" />
|
||||
|
||||
<Control MinSize="0 20"/>
|
||||
|
||||
<CheckBox Name="Preview" Text="{Loc 'admin-explosion-eui-label-preview'}"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-total'}" MinSize="120 0"/>
|
||||
<FloatSpinBox Name="Intensity" MinSize="130 0" HorizontalExpand="True" Value="200"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-slope'}" MinSize="120 0" />
|
||||
<FloatSpinBox Name="Slope" MinSize="130 0" HorizontalExpand="True" Value="5"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'admin-explosion-eui-label-max'}" MinSize="120 0" />
|
||||
<FloatSpinBox Name="MaxIntensity" MinSize="130 0" HorizontalExpand="True" Value="100"/>
|
||||
</BoxContainer>
|
||||
|
||||
<Control MinSize="0 20"/>
|
||||
|
||||
<Button Name="Spawn" Text="{Loc 'admin-explosion-eui-label-spawn'}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,142 @@
|
||||
using Content.Shared.Explosion;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.OptionButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.SpawnExplosion;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
[UsedImplicitly]
|
||||
public sealed partial class SpawnExplosionWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
|
||||
private readonly SpawnExplosionEui _eui;
|
||||
private List<MapId> _mapData = new();
|
||||
private List<string> _explosionTypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent unnecessary preview updates when setting fields (e.g., updating position)..
|
||||
/// </summary>
|
||||
private bool _pausePreview;
|
||||
|
||||
public SpawnExplosionWindow(SpawnExplosionEui eui)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_eui = eui;
|
||||
|
||||
ExplosionOption.OnItemSelected += ExplosionSelected;
|
||||
MapOptions.OnItemSelected += MapSelected;
|
||||
Recentre.OnPressed += (_) => SetLocation();
|
||||
Spawn.OnPressed += SubmitButtonOnOnPressed;
|
||||
|
||||
Preview.OnToggled += (_) => UpdatePreview();
|
||||
MapX.OnValueChanged += (_) => UpdatePreview();
|
||||
MapY.OnValueChanged += (_) => UpdatePreview();
|
||||
Intensity.OnValueChanged += (_) => UpdatePreview();
|
||||
Slope.OnValueChanged += (_) => UpdatePreview();
|
||||
MaxIntensity.OnValueChanged += (_) => UpdatePreview();
|
||||
}
|
||||
|
||||
private void ExplosionSelected(ItemSelectedEventArgs args)
|
||||
{
|
||||
ExplosionOption.SelectId(args.Id);
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
private void MapSelected(ItemSelectedEventArgs args)
|
||||
{
|
||||
MapOptions.SelectId(args.Id);
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
SetLocation();
|
||||
UpdateExplosionTypeOptions();
|
||||
}
|
||||
|
||||
private void UpdateExplosionTypeOptions()
|
||||
{
|
||||
_explosionTypes.Clear();
|
||||
ExplosionOption.Clear();
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<ExplosionPrototype>())
|
||||
{
|
||||
_explosionTypes.Add(type.ID);
|
||||
ExplosionOption.AddItem(type.ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMapOptions()
|
||||
{
|
||||
_mapData.Clear();
|
||||
MapOptions.Clear();
|
||||
foreach (var map in _mapManager.GetAllMapIds())
|
||||
{
|
||||
_mapData.Add(map);
|
||||
MapOptions.AddItem(map.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the current grid & indices based on the attached entities current location.
|
||||
/// </summary>
|
||||
private void SetLocation()
|
||||
{
|
||||
UpdateMapOptions();
|
||||
|
||||
if (!_entMan.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out TransformComponent? transform))
|
||||
return;
|
||||
|
||||
_pausePreview = true;
|
||||
MapOptions.Select(_mapData.IndexOf(transform.MapID));
|
||||
(MapX.Value, MapY.Value) = transform.MapPosition.Position;
|
||||
_pausePreview = false;
|
||||
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
private void UpdatePreview()
|
||||
{
|
||||
if (_pausePreview)
|
||||
return;
|
||||
|
||||
if (!Preview.Pressed)
|
||||
{
|
||||
_eui.ClearOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
MapCoordinates coords = new((MapX.Value, MapY.Value), _mapData[MapOptions.SelectedId]);
|
||||
var explosionType = _explosionTypes[ExplosionOption.SelectedId];
|
||||
_eui.RequestPreviewData(coords, explosionType, Intensity.Value, Slope.Value, MaxIntensity.Value);
|
||||
}
|
||||
|
||||
private void SubmitButtonOnOnPressed(ButtonEventArgs args)
|
||||
{
|
||||
// need to make room to view the fireworks
|
||||
Preview.Pressed = false;
|
||||
_eui.ClearOverlay();
|
||||
|
||||
// for the actual explosion, we will just re-use the explosion command.
|
||||
// so assemble command arguments:
|
||||
var mapId = _mapData[MapOptions.SelectedId];
|
||||
var explosionType = _explosionTypes[ExplosionOption.SelectedId];
|
||||
var cmd = $"explosion {Intensity.Value} {Slope.Value} {MaxIntensity.Value} {MapX.Value} {MapY.Value} {mapId} {explosionType}";
|
||||
|
||||
_conHost.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Entry
|
||||
"Temperature",
|
||||
"AtmosExposed",
|
||||
"Explosive",
|
||||
"ExplosionResistance",
|
||||
"Vocal",
|
||||
"OnUseTimerTrigger",
|
||||
"WarpPoint",
|
||||
|
||||
120
Content.Client/Explosion/ExplosionOverlay.cs
Normal file
120
Content.Client/Explosion/ExplosionOverlay.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Explosion;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ExplosionOverlay : Overlay
|
||||
{
|
||||
/// <summary>
|
||||
/// The explosion that needs to be drawn. This explosion is currently being processed by the server and
|
||||
/// expanding outwards.
|
||||
/// </summary>
|
||||
internal Explosion? ActiveExplosion;
|
||||
|
||||
/// <summary>
|
||||
/// This index specifies what parts of the currently expanding explosion should be drawn.
|
||||
/// </summary>
|
||||
public int Index;
|
||||
|
||||
/// <summary>
|
||||
/// These explosions have finished expanding, but we will draw for a few more frames. This is important for
|
||||
/// small explosions, as otherwise they disappear far too quickly.
|
||||
/// </summary>
|
||||
internal List<Explosion> CompletedExplosions = new ();
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
/// <summary>
|
||||
/// How intense does the explosion have to be at a tile to advance to the next fire texture state?
|
||||
/// </summary>
|
||||
public const int IntensityPerState = 12;
|
||||
|
||||
private ShaderInstance _shader;
|
||||
|
||||
public ExplosionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var drawHandle = args.WorldHandle;
|
||||
drawHandle.UseShader(_shader);
|
||||
|
||||
if (ActiveExplosion != null)
|
||||
{
|
||||
DrawExplosion(drawHandle, args.WorldBounds, ActiveExplosion, Index);
|
||||
}
|
||||
|
||||
foreach (var exp in CompletedExplosions)
|
||||
{
|
||||
DrawExplosion(drawHandle, args.WorldBounds, exp, exp.Intensity.Count);
|
||||
}
|
||||
|
||||
drawHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
|
||||
private void DrawExplosion(DrawingHandleWorld drawHandle, Box2Rotated worldBounds, Explosion exp, int index)
|
||||
{
|
||||
if (exp.Map != _eyeManager.CurrentMap)
|
||||
return;
|
||||
|
||||
Box2 gridBounds;
|
||||
foreach (var (gridId, tiles) in exp.Tiles)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
continue;
|
||||
|
||||
gridBounds = grid.InvWorldMatrix.TransformBox(worldBounds);
|
||||
drawHandle.SetTransform(grid.WorldMatrix);
|
||||
|
||||
DrawTiles(drawHandle, gridBounds, index, tiles, exp);
|
||||
}
|
||||
|
||||
if (exp.SpaceTiles == null)
|
||||
return;
|
||||
|
||||
gridBounds = Matrix3.Invert(exp.SpaceMatrix).TransformBox(worldBounds);
|
||||
drawHandle.SetTransform(exp.SpaceMatrix);
|
||||
|
||||
DrawTiles(drawHandle, gridBounds, index, exp.SpaceTiles, exp);
|
||||
}
|
||||
|
||||
private void DrawTiles(
|
||||
DrawingHandleWorld drawHandle,
|
||||
Box2 gridBounds,
|
||||
int index,
|
||||
Dictionary<int, List<Vector2i>> tileSets,
|
||||
Explosion exp)
|
||||
{
|
||||
for (var j = 0; j < index; j++)
|
||||
{
|
||||
if (!tileSets.TryGetValue(j, out var tiles))
|
||||
continue;
|
||||
|
||||
var frameIndex = (int) Math.Min(exp.Intensity[j] / IntensityPerState, exp.FireFrames.Count - 1);
|
||||
var frames = exp.FireFrames[frameIndex];
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
Vector2 bottomLeft = (tile.X, tile.Y);
|
||||
|
||||
if (!gridBounds.Contains((bottomLeft + 0.5f) * 1f))
|
||||
continue;
|
||||
|
||||
var texture = _robustRandom.Pick(frames);
|
||||
drawHandle.DrawTexture(texture, bottomLeft, exp.FireColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
Content.Client/Explosion/ExplosionOverlaySystem.cs
Normal file
190
Content.Client/Explosion/ExplosionOverlaySystem.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Content.Shared.Explosion;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Explosion;
|
||||
|
||||
/// <summary>
|
||||
/// This system is responsible for showing the client-side explosion effects (light source & fire-overlay). The
|
||||
/// fire overlay code is just a bastardized version of the atmos plasma fire overlay and uses the same texture.
|
||||
/// </summary>
|
||||
public sealed class ExplosionOverlaySystem : EntitySystem
|
||||
{
|
||||
private ExplosionOverlay _overlay = default!;
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For how many seconds should an explosion stay on-screen once it has finished expanding?
|
||||
/// </summary>
|
||||
public const float ExplosionPersistence = 0.3f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<ExplosionEvent>(OnExplosion);
|
||||
SubscribeNetworkEvent<ExplosionOverlayUpdateEvent>(HandleExplosionUpdate);
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new ExplosionOverlay();
|
||||
if (!overlayManager.HasOverlay<ExplosionOverlay>())
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
// increment the lifetime of completed explosions, and remove them if they have been ons screen for more
|
||||
// than ExplosionPersistence seconds
|
||||
foreach (var explosion in _overlay.CompletedExplosions.ToArray())
|
||||
{
|
||||
explosion.Lifetime += frameTime;
|
||||
|
||||
if (explosion.Lifetime >= ExplosionPersistence)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(explosion.LightEntity);
|
||||
_overlay.CompletedExplosions.Remove(explosion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The server has processed some explosion. This updates the client-side overlay so that the area covered
|
||||
/// by the fire-visual matches up with the area that the explosion has affected.
|
||||
/// </summary>
|
||||
private void HandleExplosionUpdate(ExplosionOverlayUpdateEvent args)
|
||||
{
|
||||
if (args.ExplosionId != _overlay.ActiveExplosion?.Explosionid && !IsNewer(args.ExplosionId))
|
||||
{
|
||||
// out of order events. Ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
_overlay.Index = args.Index;
|
||||
|
||||
if (_overlay.ActiveExplosion == null)
|
||||
{
|
||||
// no active explosion... events out of order?
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Index != int.MaxValue)
|
||||
return;
|
||||
|
||||
// the explosion has finished expanding
|
||||
_overlay.Index = 0;
|
||||
_overlay.CompletedExplosions.Add(_overlay.ActiveExplosion);
|
||||
_overlay.ActiveExplosion = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new explosion occurred. This prepares the client-side light entity and stores the
|
||||
/// explosion/fire-effect overlay data.
|
||||
/// </summary>
|
||||
private void OnExplosion(ExplosionEvent args)
|
||||
{
|
||||
if (!_protoMan.TryIndex(args.TypeID, out ExplosionPrototype? type))
|
||||
return;
|
||||
|
||||
// spawn in a light source at the epicenter
|
||||
var lightEntity = Spawn("ExplosionLight", args.Epicenter);
|
||||
var light = EnsureComp<PointLightComponent>(lightEntity);
|
||||
light.Energy = light.Radius = args.Intensity.Count;
|
||||
light.Color = type.LightColor;
|
||||
|
||||
if (_overlay.ActiveExplosion == null)
|
||||
{
|
||||
_overlay.ActiveExplosion = new(args, type, lightEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
// we have a currently active explosion. Can happen when events are received out of order. either multiple
|
||||
// explosions are happening in one tick, or a new explosion was received before the event telling us the old one
|
||||
// finished got through.
|
||||
|
||||
if (IsNewer(args.ExplosionId))
|
||||
{
|
||||
// This is a newer explosion. Add the old-currently-active explosions to the completed list
|
||||
_overlay.CompletedExplosions.Add(_overlay.ActiveExplosion);
|
||||
_overlay.ActiveExplosion = new(args, type, lightEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// explosions were out of order. keep the active one, and directly add the received one to the completed
|
||||
// list.
|
||||
_overlay.CompletedExplosions.Add(new(args, type, lightEntity));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNewer(byte explosionId)
|
||||
{
|
||||
if (_overlay.ActiveExplosion == null)
|
||||
return true;
|
||||
|
||||
// byte goes up to 255, then wraps back to zero.
|
||||
// what are the odds of more than 128 explosions happening in a tick?
|
||||
return _overlay.ActiveExplosion.Explosionid < explosionId
|
||||
|| _overlay.ActiveExplosion.Explosionid > 192 && explosionId < 64;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if (overlayManager.HasOverlay<ExplosionOverlay>())
|
||||
overlayManager.RemoveOverlay<ExplosionOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Explosion
|
||||
{
|
||||
public Dictionary<int, List<Vector2i>>? SpaceTiles;
|
||||
public Dictionary<GridId, Dictionary<int, List<Vector2i>>> Tiles;
|
||||
public List<float> Intensity;
|
||||
public EntityUid LightEntity;
|
||||
public MapId Map;
|
||||
public byte Explosionid;
|
||||
|
||||
public Matrix3 SpaceMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// How long have we been drawing this explosion, starting from the time the explosion was fully drawn.
|
||||
/// </summary>
|
||||
public float Lifetime;
|
||||
|
||||
/// <summary>
|
||||
/// The textures used for the explosion fire effect. Each fire-state is associated with an explosion
|
||||
/// intensity range, and each stat itself has several textures.
|
||||
/// </summary>
|
||||
public List<Texture[]> FireFrames = new();
|
||||
|
||||
public Color? FireColor;
|
||||
|
||||
internal Explosion(ExplosionEvent args, ExplosionPrototype type, EntityUid lightEntity)
|
||||
{
|
||||
Map = args.Epicenter.MapId;
|
||||
SpaceTiles = args.SpaceTiles;
|
||||
Tiles = args.Tiles;
|
||||
Intensity = args.Intensity;
|
||||
SpaceMatrix = args.SpaceMatrix;
|
||||
Explosionid = args.ExplosionId;
|
||||
FireColor = type.FireColor;
|
||||
LightEntity = lightEntity;
|
||||
|
||||
var fireRsi = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(type.TexturePath).RSI;
|
||||
foreach (var state in fireRsi)
|
||||
{
|
||||
FireFrames.Add(state.GetFrames(RSI.State.Direction.South));
|
||||
if (FireFrames.Count == type.FireStates)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user