Files
tbd-station-14/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
2025-10-29 02:54:42 +00:00

264 lines
7.9 KiB
C#

using System.Numerics;
using Content.Client.Resources;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
namespace Content.Client.Weapons.Ranged.ItemStatus;
public abstract class BaseBulletRenderer : Control
{
private int _capacity;
private LayoutParameters _params;
public int Rows { get; set; } = 2;
public int Count { get; set; }
public int Capacity
{
get => _capacity;
set
{
if (_capacity == value)
return;
_capacity = value;
InvalidateMeasure();
}
}
protected LayoutParameters Parameters
{
get => _params;
set
{
_params = value;
InvalidateMeasure();
}
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var countPerRow = Math.Min(Capacity, CountPerRow(availableSize.X));
var rows = Math.Min((int) MathF.Ceiling(Capacity / (float) countPerRow), Rows);
var height = _params.ItemHeight * rows + (_params.VerticalSeparation * rows - 1);
var width = RowWidth(countPerRow);
return new Vector2(width, height);
}
protected override void Draw(DrawingHandleScreen handle)
{
// Scale rendering in this control by UIScale.
var currentTransform = handle.GetTransform();
handle.SetTransform(Matrix3Helpers.CreateScale(new Vector2(UIScale)) * currentTransform);
var countPerRow = CountPerRow(Size.X);
var pos = new Vector2();
var spent = Capacity - Count;
var bulletsDone = 0;
// Draw by rows, bottom to top.
for (var row = 0; row < Rows; row++)
{
var altColor = false;
var thisRowCount = Math.Min(countPerRow, Capacity - bulletsDone);
if (thisRowCount <= 0)
break;
// Handle MinCountPerRow
// We only do this if:
// 1. The next row would have less than MinCountPerRow bullets.
// 2. The next row is actually visible (we aren't the last row).
// 3. MinCountPerRow is actually smaller than the count per row (avoid degenerate cases).
// 4. There's enough bullets that at least one will end up on the next row.
var nextRowCount = Capacity - bulletsDone - thisRowCount;
if (nextRowCount < _params.MinCountPerRow && row != Rows - 1 && _params.MinCountPerRow < countPerRow && nextRowCount > 0)
thisRowCount -= _params.MinCountPerRow - nextRowCount;
// Account for row width to right-align.
var rowWidth = RowWidth(thisRowCount);
pos.X += Size.X - rowWidth;
// Draw row left to right (so overlapping works)
for (var bullet = 0; bullet < thisRowCount; bullet++)
{
var absIdx = Capacity - bulletsDone - thisRowCount + bullet;
var renderPos = pos;
renderPos.Y = Size.Y - renderPos.Y - _params.ItemHeight;
DrawItem(handle, renderPos, absIdx < spent, altColor);
pos.X += _params.ItemSeparation;
altColor ^= true;
}
bulletsDone += thisRowCount;
pos.X = 0;
pos.Y += _params.ItemHeight + _params.VerticalSeparation;
}
}
protected abstract void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor);
private int CountPerRow(float width)
{
return (int) ((width - _params.ItemWidth + _params.ItemSeparation) / _params.ItemSeparation);
}
private int RowWidth(int count)
{
return (count - 1) * _params.ItemSeparation + _params.ItemWidth;
}
protected struct LayoutParameters
{
public int ItemHeight;
public int ItemSeparation;
public int ItemWidth;
public int VerticalSeparation;
/// <summary>
/// Try to ensure there's at least this many bullets on one row.
/// </summary>
/// <remarks>
/// For example, if there are two rows and the second row has only two bullets,
/// we "steal" some bullets from the row below it to make it look nicer.
/// </remarks>
public int MinCountPerRow;
}
}
/// <summary>
/// Renders one or more rows of bullets for item status.
/// </summary>
/// <remarks>
/// This is a custom control to allow complex responsive layout logic.
/// </remarks>
public sealed class BulletRender : BaseBulletRenderer
{
public const int MinCountPerRow = 7;
public const int BulletHeight = 12;
public const int VerticalSeparation = 2;
private static readonly LayoutParameters LayoutLarge = new LayoutParameters
{
ItemHeight = BulletHeight,
ItemSeparation = 6,
ItemWidth = 5,
VerticalSeparation = VerticalSeparation,
MinCountPerRow = MinCountPerRow
};
private static readonly LayoutParameters LayoutNormal = new LayoutParameters
{
ItemHeight = BulletHeight,
ItemSeparation = 3,
ItemWidth = 5,
VerticalSeparation = VerticalSeparation,
MinCountPerRow = MinCountPerRow
};
private static readonly LayoutParameters LayoutTiny = new LayoutParameters
{
ItemHeight = BulletHeight,
ItemSeparation = 2,
ItemWidth = 2,
VerticalSeparation = VerticalSeparation,
MinCountPerRow = MinCountPerRow
};
private static readonly Color ColorA = Color.FromHex("#b68f0e");
private static readonly Color ColorB = Color.FromHex("#d7df60");
private static readonly Color ColorGoneA = Color.FromHex("#000000");
private static readonly Color ColorGoneB = Color.FromHex("#222222");
private readonly Texture _bulletTiny;
private readonly Texture _bulletNormal;
private BulletType _type = BulletType.Normal;
public BulletType Type
{
get => _type;
set
{
if (_type == value)
return;
Parameters = value switch
{
BulletType.Large => LayoutLarge,
BulletType.Normal => LayoutNormal,
BulletType.Tiny => LayoutTiny,
_ => throw new ArgumentOutOfRangeException()
};
_type = value;
}
}
public BulletRender()
{
var resC = IoCManager.Resolve<IResourceCache>();
_bulletTiny = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/tiny.png");
_bulletNormal = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/normal.png");
Parameters = LayoutNormal;
}
protected override void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor)
{
Color color;
if (spent)
color = altColor ? ColorGoneA : ColorGoneB;
else
color = altColor ? ColorA : ColorB;
var texture = _type == BulletType.Tiny ? _bulletTiny : _bulletNormal;
handle.DrawTexture(texture, renderPos, color);
}
public enum BulletType
{
Large,
Normal,
Tiny
}
}
public sealed class BatteryBulletRenderer : BaseBulletRenderer
{
private static readonly Color ItemColor = Color.FromHex("#E00000");
private static readonly Color ItemColorGone = Color.Black;
private const int SizeH = 10;
private const int SizeV = 10;
private const int Separation = 4;
public BatteryBulletRenderer()
{
Parameters = new LayoutParameters
{
ItemWidth = SizeH,
ItemHeight = SizeV,
ItemSeparation = SizeH + Separation,
MinCountPerRow = 3,
VerticalSeparation = Separation
};
}
protected override void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor)
{
var color = spent ? ItemColorGone : ItemColor;
handle.DrawRect(UIBox2.FromDimensions(renderPos, new Vector2(SizeH, SizeV)), color);
}
}