mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-18 22:18:52 +03:00
# Description
This PR fixes all remaining bugs with Space Wind, while providing
extremely significant performance improvements by way of aggressive
early exits, and also completely reworking how pressure moved entities
are tracked(The server now caches entities under active air movements).
I haven't profiled it yet, but the performance improvements are
extremely noticeable on the machine I'm running in comparison to before,
which is particularly noteworthy since previous versions of space wind
caused significant frame drops in testing on my monster rig. While this
new update to space wind straight up doesn't even budge the tickrate on
my machine anymore.
I had to also rip out some of Monstermos' Guts, and deprecate the
tileripping system. In the future, Tile-ripping will be handled by MAS.
Also, MAS outright no longer requires Monstermos to work, and can
operate entirely off of the much cheaper LINDA system. However, while
LINDA is cheaper, Monstermos is needed for handling "Extra violent air".
Eventually I can do away with Monstermos entirely, and have it replaced
with an airflow system that outright does not require pathfinding
algorithms.
# Changelog
🆑
- fix: Fixed a bug where items thrown by space wind could remain in air
for as long as 500 seconds. Items thrown by space wind can now remain in
the air for a maximum of 2 seconds past the last time they were thrown.
- fix: DRAMATICALLY IMPROVED SPACE WIND PERFORMANCE.
- tweak: ShowAtmos command now shows vectors representing the exact
direction space wind at a given tile is trying to throw objects! The
length of the lines shown directly correspond to how powerful the flow
of air is at that tile. Shorter lines = shorter throws. Longer lines =
more powerful throws. These are also no longer restricted to compass
directions, and can point in arbitrarily any direction on a circle.
- tweak: Space Wind now no longer has Monstermos as a hard requirement.
It'll be as weak as a toddler without it, but if you're running your
server on a toaster, you can turn off Monstermos and still have working
space wind.
- tweak: Wreckages in space are now never airtight. I'm sorry, but this
was basically the cost I had to pay in exchange for making Atmos no
longer even on the top 20 systems for server tickrate cost.
(cherry picked from commit 2fcc806423a259fd75977b78350c2232a823739b)
257 lines
9.2 KiB
C#
257 lines
9.2 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.Atmos.EntitySystems;
|
|
using Content.Client.Resources;
|
|
using Content.Shared.Atmos;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.Input;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.CustomControls;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using AtmosDebugOverlayData = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData;
|
|
using DebugMessage = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayMessage;
|
|
|
|
namespace Content.Client.Atmos.Overlays;
|
|
|
|
|
|
public sealed class AtmosDebugOverlay : Overlay
|
|
{
|
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
[Dependency] private readonly IInputManager _input = default!;
|
|
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
|
[Dependency] private readonly IResourceCache _cache = default!;
|
|
private readonly SharedTransformSystem _transform;
|
|
private readonly AtmosDebugOverlaySystem _system;
|
|
private readonly SharedMapSystem _map;
|
|
private readonly Font _font;
|
|
private List<(Entity<MapGridComponent>, DebugMessage)> _grids = new();
|
|
|
|
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
|
|
|
internal AtmosDebugOverlay(AtmosDebugOverlaySystem system)
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
_system = system;
|
|
_transform = _entManager.System<SharedTransformSystem>();
|
|
_map = _entManager.System<SharedMapSystem>();
|
|
_font = _cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
|
|
}
|
|
|
|
protected override void Draw(in OverlayDrawArgs args)
|
|
{
|
|
if (args.Space == OverlaySpace.ScreenSpace)
|
|
{
|
|
DrawTooltip(args);
|
|
return;
|
|
}
|
|
|
|
var handle = args.WorldHandle;
|
|
GetGrids(args.MapId, args.WorldBounds);
|
|
|
|
// IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE:
|
|
// -- THINK! --
|
|
// 1. "Is this going to make a critical atmos debugging tool harder to debug itself?"
|
|
// 2. "Is this going to do anything that could cause the atmos debugging tool to use resources, server-side or client-side, when nobody's using it?"
|
|
// 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?"
|
|
// Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc
|
|
|
|
foreach (var (grid, msg) in _grids)
|
|
{
|
|
handle.SetTransform(_transform.GetWorldMatrix(grid));
|
|
DrawData(msg, handle);
|
|
}
|
|
|
|
handle.SetTransform(Matrix3x2.Identity);
|
|
}
|
|
|
|
private void DrawData(DebugMessage msg,
|
|
DrawingHandleWorld handle)
|
|
{
|
|
foreach (var data in msg.OverlayData)
|
|
{
|
|
if (data != null)
|
|
DrawGridTile(data.Value, handle);
|
|
}
|
|
}
|
|
|
|
private void DrawGridTile(AtmosDebugOverlayData data,
|
|
DrawingHandleWorld handle)
|
|
{
|
|
DrawFill(data, handle);
|
|
DrawBlocked(data, handle);
|
|
}
|
|
|
|
private void DrawFill(AtmosDebugOverlayData data, DrawingHandleWorld handle)
|
|
{
|
|
var tile = data.Indices;
|
|
var fill = GetFillData(data);
|
|
var interp = (fill - _system.CfgBase) / _system.CfgScale;
|
|
|
|
Color res;
|
|
if (_system.CfgCBM)
|
|
{
|
|
// Greyscale interpolation
|
|
res = Color.InterpolateBetween(Color.Black, Color.White, interp);
|
|
}
|
|
else
|
|
{
|
|
// Red-Green-Blue interpolation
|
|
if (interp < 0.5f)
|
|
{
|
|
res = Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2);
|
|
}
|
|
else
|
|
{
|
|
res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
|
|
}
|
|
}
|
|
|
|
res = res.WithAlpha(0.75f);
|
|
handle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
|
|
}
|
|
|
|
private float GetFillData(AtmosDebugOverlayData data)
|
|
{
|
|
if (data.Moles == null)
|
|
return 0;
|
|
|
|
switch (_system.CfgMode)
|
|
{
|
|
case AtmosDebugOverlayMode.TotalMoles:
|
|
var total = 0f;
|
|
foreach (var f in data.Moles)
|
|
{
|
|
total += f;
|
|
}
|
|
|
|
return total;
|
|
case AtmosDebugOverlayMode.GasMoles:
|
|
return data.Moles[_system.CfgSpecificGas];
|
|
default:
|
|
return data.Temperature;
|
|
}
|
|
}
|
|
|
|
private void DrawBlocked(AtmosDebugOverlayData data, DrawingHandleWorld handle)
|
|
{
|
|
var tile = data.Indices;
|
|
var tileCentre = tile + 0.5f * Vector2.One;
|
|
CheckAndShowBlockDir(data, handle, AtmosDirection.North, tileCentre);
|
|
CheckAndShowBlockDir(data, handle, AtmosDirection.South, tileCentre);
|
|
CheckAndShowBlockDir(data, handle, AtmosDirection.East, tileCentre);
|
|
CheckAndShowBlockDir(data, handle, AtmosDirection.West, tileCentre);
|
|
DrawPressureDirection(handle, data.LastPressureDirection, tileCentre, Color.Blue);
|
|
|
|
// -- Excited Groups --
|
|
if (data.InExcitedGroup is {} grp)
|
|
{
|
|
var basisA = tile;
|
|
var basisB = tile + new Vector2(1.0f, 1.0f);
|
|
var basisC = tile + new Vector2(0.0f, 1.0f);
|
|
var basisD = tile + new Vector2(1.0f, 0.0f);
|
|
var color = Color.White // Use first three nibbles for an unique color... Good enough?
|
|
.WithRed(grp & 0x000F)
|
|
.WithGreen((grp & 0x00F0) >> 4)
|
|
.WithBlue((grp & 0x0F00) >> 8);
|
|
handle.DrawLine(basisA, basisB, color);
|
|
handle.DrawLine(basisC, basisD, color);
|
|
}
|
|
|
|
if (data.IsSpace)
|
|
handle.DrawCircle(tileCentre, 0.15f, Color.Yellow);
|
|
|
|
if (data.MapAtmosphere)
|
|
handle.DrawCircle(tileCentre, 0.1f, Color.Orange);
|
|
|
|
if (data.NoGrid)
|
|
handle.DrawCircle(tileCentre, 0.05f, Color.Black);
|
|
}
|
|
|
|
private void CheckAndShowBlockDir(AtmosDebugOverlayData data, DrawingHandleWorld handle, AtmosDirection dir,
|
|
Vector2 tileCentre)
|
|
{
|
|
if (!data.BlockDirection.HasFlag(dir))
|
|
return;
|
|
|
|
// Account for South being 0.
|
|
var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90);
|
|
var atmosAngleOfs = atmosAngle.ToVec() * 0.45f;
|
|
var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X);
|
|
var basisA = tileCentre + atmosAngleOfs - atmosAngleOfsR90;
|
|
var basisB = tileCentre + atmosAngleOfs + atmosAngleOfsR90;
|
|
handle.DrawLine(basisA, basisB, Color.Azure);
|
|
}
|
|
|
|
private void DrawPressureDirection(DrawingHandleWorld handle, Vector2 lastPressureDirection, Vector2 center, Color color) =>
|
|
handle.DrawLine(center, center + lastPressureDirection, color);
|
|
|
|
private void DrawTooltip(in OverlayDrawArgs args)
|
|
{
|
|
var handle = args.ScreenHandle;
|
|
var mousePos = _input.MouseScreenPosition;
|
|
if (!mousePos.IsValid)
|
|
return;
|
|
|
|
if (_ui.MouseGetControl(mousePos) is not IViewportControl viewport)
|
|
return;
|
|
|
|
var coords = viewport.PixelToMap(mousePos.Position);
|
|
var box = Box2.CenteredAround(coords.Position, 3 * Vector2.One);
|
|
GetGrids(coords.MapId, new Box2Rotated(box));
|
|
|
|
foreach (var (grid, msg) in _grids)
|
|
{
|
|
var index = _map.WorldToTile(grid, grid, coords.Position);
|
|
foreach (var data in msg.OverlayData)
|
|
{
|
|
if (data?.Indices == index)
|
|
{
|
|
DrawTooltip(handle, mousePos.Position, data.Value);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawTooltip(DrawingHandleScreen handle, Vector2 pos, AtmosDebugOverlayData data)
|
|
{
|
|
var lineHeight = _font.GetLineHeight(1f);
|
|
var offset = new Vector2(0, lineHeight);
|
|
|
|
var moles = data.Moles == null
|
|
? "No Air"
|
|
: data.Moles.Sum().ToString();
|
|
|
|
handle.DrawString(_font, pos, $"Moles: {moles}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"Temp: {data.Temperature}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"Excited: {data.InExcitedGroup?.ToString() ?? "None"}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"Space: {data.IsSpace}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"Map: {data.MapAtmosphere}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"NoGrid: {data.NoGrid}");
|
|
pos += offset;
|
|
handle.DrawString(_font, pos, $"Immutable: {data.Immutable}");
|
|
}
|
|
|
|
private void GetGrids(MapId mapId, Box2Rotated box)
|
|
{
|
|
_grids.Clear();
|
|
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, (EntityUid uid, MapGridComponent grid,
|
|
ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
|
|
{
|
|
if (_system.TileData.TryGetValue(uid, out var data))
|
|
state.Add(((uid, grid), data));
|
|
return true;
|
|
});
|
|
}
|
|
}
|