using System;
using System.Collections.Generic;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
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.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Debugging.Overlays;
///
/// This is an abstract helper class that can be used to create simple debug overlays that need to render tile based data.
///
[UsedImplicitly]
public abstract partial class TileDebugOverlay : Overlay, IPostInjectInit
{
[Dependency] protected IEntityManager Entity = default!;
[Dependency] protected IEyeManager Eye = default!;
[Dependency] protected IInputManager Input = default!;
[Dependency] protected IUserInterfaceManager Ui = default!;
[Dependency] protected IResourceCache Cache = default!;
protected SharedTransformSystem Transform = default!;
protected MapSystem Map = default!;
protected EntityLookupSystem Lookup = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
protected Font Font = default!;
protected List> Grids = new();
void IPostInjectInit.PostInject()
{
Transform = Entity.System();
Map = Entity.System();
Lookup = Entity.System();
var font = Cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf");
Font = new VectorFont(font, 8);
Init();
}
protected virtual void Init()
{
}
protected internal override void Draw(in OverlayDrawArgs args)
{
Grids.Clear();
if (args.Viewport.Eye?.Position.MapId is not {} map || map == MapId.Nullspace)
return;
Map.FindGridsIntersecting(map, args.WorldBounds, ref Grids);
foreach (var grid in Grids)
{
switch (args.Space)
{
case OverlaySpace.ScreenSpace:
DrawScreen(args, grid);
break;
case OverlaySpace.WorldSpace:
DrawWorld(args, grid);
break;
}
}
Grids.Clear();
}
protected virtual void DrawScreen(in OverlayDrawArgs args, Entity grid)
{
var handle = args.ScreenHandle;
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
foreach (var tile in Map.GetLocalTilesIntersecting(grid, grid, gridBounds))
{
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
if (!gridBounds.Intersects(tileBounds))
continue;
var screenTileCentre = Eye.WorldToScreen(Vector2.Transform(tileBounds.Center, matrix));
DrawTileText(handle, screenTileCentre, tile.GridIndices, grid);
}
// Draw mouse tooltip
DrawTooltip(handle);
}
protected virtual void DrawTooltip(DrawingHandleScreen handle)
{
var mousePos = Input.MouseScreenPosition;
if (!mousePos.IsValid)
return;
if (Ui.MouseGetControl(mousePos) is not IViewportControl viewport)
return;
var coords = viewport.PixelToMap(mousePos.Position);
if (!Map.TryFindGridAt(coords, out var grid, out var comp))
return;
var local = Map.WorldToLocal(grid, comp, coords.Position);
var x = (int) Math.Floor(local.X / comp.TileSize);
var y = (int) Math.Floor(local.Y / comp.TileSize);
var indices = new Vector2i(x, y);
DrawTooltip(handle, mousePos.Position, local, indices, (grid, comp));
}
///
/// Draw a tooltip around the mouse
///
/// The mouse's screen coordinates
/// The mouse's local grid coordinates
/// The mouse's tile indices
/// The grid that the mouse is hovering over
protected virtual void DrawTooltip(DrawingHandleScreen handle, Vector2 mouseScreen, Vector2 mouseLocal, Vector2i indices, Entity grid)
{
if (GetTooltip(mouseLocal, indices, grid) is not { } text)
return;
var lineHeight = Font.GetLineHeight(1f);
var offset = new Vector2(0, lineHeight);
handle.DrawString(Font, mouseScreen - offset, text);
}
protected virtual void DrawTileText(DrawingHandleScreen handle, Vector2 tileCentre, Vector2i indices, Entity grid)
{
if (GetText(indices, grid) is {} text)
handle.DrawString(Font, tileCentre, text);
}
protected virtual void DrawWorld(in OverlayDrawArgs args, Entity grid)
{
var handle = args.WorldHandle;
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
foreach (var tile in Map.GetLocalTilesIntersecting(grid, grid, gridBounds))
{
handle.SetTransform(matrix);
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
if (gridBounds.Intersects(tileBounds))
DrawTile(handle, tileBounds, tile.GridIndices, grid);
}
handle.SetTransform(Matrix3x2.Identity);
}
protected virtual void DrawTile(DrawingHandleWorld handle, Box2 tile, Vector2i indices, Entity grid)
{
if (GetColor(indices, grid) is not { } color)
return;
handle.DrawRect(tile, color.Border, filled: false);
handle.DrawRect(tile, color.Fill, filled: true);
}
///
/// Get text that will be rendered in a grid tile.
///
protected abstract string? GetText(Vector2i indices, Entity grid);
///
/// Get tooltip text that will be shown next to the mouse.
///
/// The mouse's position relative to the grid.
/// The grid indices corresponding to the mouse's position
/// The grid that the mouse is over.
protected abstract string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity grid);
///
/// Get a border & fill color that will be used to draw a grid tile.
///
protected abstract (Color Fill, Color Border)? GetColor(Vector2i indices, Entity grid);
}
///
/// Variant of that exists to draw simple float information for each tile.
///
public abstract class TileFloatDebugOverlay : TileDebugOverlay
{
protected virtual float MinValue => 0;
protected virtual float MaxValue => 1;
protected abstract float? GetData(Vector2i indices, Entity grid);
protected override string? GetText(Vector2i indices, Entity grid)
{
return GetData(indices, grid)?.ToString("F2");
}
protected override string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity grid)
{
return GetData(indices, grid)?.ToString("F2");
}
protected override (Color Fill, Color Border)? GetColor(Vector2i indices, Entity grid)
{
if (GetData(indices, grid) is not { } value)
return null;
var color = Gradient(value, MinValue, MaxValue);
return (color.WithAlpha(0.2f), color);
}
///
/// Simple yellow -> orange -> red gradient.
///
public Color Gradient(float value, float min, float max)
{
// map min to 1, max to 0
value = (value - min) / (max - min);
return value < 0.5f
? Color.InterpolateBetween(Color.Yellow, Color.Orange, value * 2)
: Color.InterpolateBetween(Color.Orange, Color.Red, (value - 0.5f) * 2);
}
}