using System; using System.ComponentModel.DataAnnotations; namespace NHSE.Core; public sealed record MapViewState { private const int MaxX = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorWidth; private const int MaxY = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorHeight; private const int EdgeBuffer = LayerFieldItem.TilesPerAcreDim; /// /// Amount the X/Y coordinates change when using arrow movement. /// [Range(1, 8)] public uint ArrowViewInterval { get; set; } = 2; [Range(0, 1)] public int ItemLayerIndex { get; set { if (value is not (0 or 1)) throw new ArgumentOutOfRangeException(nameof(value), "Item layer index must be 0 or 1."); field = value; } } /// /// Top-left-origin X coordinate of the view. /// public int X { get; private set { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxX); field = value; } } /// /// Top-left-origin Y coordinate of the view. /// public int Y { get; private set { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxY); field = value; } } public bool CanUp => Y != 0; public bool CanDown => Y != MaxY; public bool CanLeft => X != 0; public bool CanRight => X != MaxX; /// /// Moves the view up by tiles. /// /// if the view changed; otherwise, . public bool ArrowUp() { if (!CanUp) return false; Y = Math.Max(0, Y - (int)ArrowViewInterval); return true; } /// /// Moves the view left by tiles. /// /// if the view changed; otherwise, . public bool ArrowLeft() { if (!CanLeft) return false; X = Math.Max(0, X - (int)ArrowViewInterval); return true; } /// /// Moves the view right by tiles. /// /// if the view changed; otherwise, . public bool ArrowRight() { if (!CanRight) return false; X = Math.Min(MaxX - EdgeBuffer, X + (int)ArrowViewInterval); return true; } /// /// Moves the view down by tiles. /// /// if the view changed; otherwise, . public bool ArrowDown() { if (!CanDown) return false; Y = Math.Min(MaxY - EdgeBuffer, Y + (int)ArrowViewInterval); return true; } /// /// Applies the requested coordinates (sanity checked). /// /// if the view changed; otherwise, . public bool SetViewTo(in int absX, in int absY) { var (x, y) = (X, Y); X = Math.Clamp(absX, 0, MaxX - EdgeBuffer); Y = Math.Clamp(absY, 0, MaxY - EdgeBuffer); return x != X || y != Y; } /// /// Drags the view by the specified delta amounts. /// /// if the view changed; otherwise, . public bool DragView(int dX, int dY) => SetViewTo(X + dX, Y + dY); /// /// Sets the view to the top-left of the specified acre. /// /// /// Acres are ordered Y-down-first. /// /// Acre index to set the view to. public void SetViewToAcre(int acre) { var acreX = acre % (MaxX / EdgeBuffer); var acreY = acre / (MaxX / EdgeBuffer); SetViewTo(acreX * EdgeBuffer, acreY * EdgeBuffer); } public (int X, int Y) EnforceEdgeBuffer(int x, int y) { x = Math.Clamp(x, 0, MaxX - EdgeBuffer); y = Math.Clamp(y, 0, MaxY - EdgeBuffer); return (x, y); } public bool IsWithinView(int x, int y, int tileStride) { if (x < X || x >= X + tileStride) return false; if (y < Y || y >= Y + tileStride) return false; return true; } }