using System.Diagnostics.CodeAnalysis;
namespace NHSE.Core;
///
/// Configures how a layer rests within the Map's grid, relative to a "chunk" or "acre".
///
/// Number of acres in the width direction.
/// Number of acres in the height direction.
/// Horizontal acre shift from the map's origin.
/// Vertical acre shift from the map's origin.
/// Number of tiles per acre in one dimension (16 or 32).
/// Bit shift value to convert between tiles and acres (4 for 16 tiles, 5 for 32 tiles).
/// Size of tile compared to the smallest tile possible (2 for 16 tiles, 1 for 32 tiles).
public readonly record struct LayerPositionConfig(
byte CountWidth, byte CountHeight,
byte ShiftWidth, byte ShiftHeight,
[ConstantExpected] byte TilesPerAcre, byte TileBitShift,
[ConstantExpected(Max = 2, Min = 1)] byte MetaTileSize)
{
// Maps in Animal Crossing: New Horizons are made up of acres that are 9 tiles wide and 8 tiles high.
// 5 columns in the center are land, surrounded by 2 tiles of beach and 2 tiles of sea on each side.
// 4 rows in the center are land, surrounded by 2 rows of beach and 2 rows of sea on each side.
// +-----------+
// | ~~~~~~~~~ |
// | ~*******~ |
// | ~*=====*~ |
// | ~*=====*~ |
// | ~*=====*~ |
// | ~*=====*~ |
// | ~*******~ |
// | ~~~~~~~~~ |
// +-----------+
// Main Island Map Config - True Dimensions
private const byte MapAcreWidth = 9; // 2 sea, 2 beach, 5 land
private const byte MapAcreHeight = 8; // 2 sea, 2 beach, 4 land
// Optimize some calculations away by using bit-shift instead of mul/div, as we're always a multiple of 2.
private const byte Grid32 = 32;
private const byte Grid16 = 16;
private const byte Shift32 = 5; // div32 is same as sh 5
private const byte Shift16 = 4; // div16 is same as sh 4
///
/// Creates a new instance, centering the layer within the acre.
///
/// Width of the layer in acres.
/// Height of the layer in acres.
/// Number of tiles per acre (16 or 32).
/// Size of tile compared to the smallest tile possible (2 for 16 tiles, 1 for 32 tiles).
/// Acre-wise horizontal shift from the map origin.
/// Acre-wise vertical shift from the map origin.
/// A new instance.
public static LayerPositionConfig Create(byte width, byte height,
[ConstantExpected(Min = Grid16, Max = Grid32)] byte tilesPerAcre,
[ConstantExpected(Min = 1, Max = 2)] byte metaTileSize, byte shiftW, byte shiftH)
{
var bitShift = tilesPerAcre == Grid16 ? Shift16 : Shift32;
#pragma warning disable CA1857
return new LayerPositionConfig(width, height, shiftW, shiftH, tilesPerAcre, bitShift, metaTileSize);
#pragma warning restore CA1857
}
///
/// Calculates the absolute map coordinates based on the specified relative X and Y coordinates within the layer.
///
/// The relative X-coordinate within the layer.
/// The relative Y-coordinate within the layer.
public (int X, int Y) GetCoordinatesAbsolute(int relX, int relY)
{
var absX = relX + ((ShiftWidth * MetaTileSize) << TileBitShift);
var absY = relY + ((ShiftHeight * MetaTileSize) << TileBitShift);
return (absX, absY);
}
///
/// Gets the absolute coordinates of the layer's origin (0,0) in the map.
///
public (int X, int Y) GetCoordinatesAbsolute() => GetCoordinatesAbsolute(0, 0);
///
/// Calculates the relative coordinates within the layer, based on the specified absolute X and Y coordinates.
///
/// The absolute X coordinate to convert.
/// The absolute Y coordinate to convert.
/// A tuple containing the X and Y coordinates relative to the layer.
public (int X, int Y) GetCoordinatesRelative(int absX, int absY)
{
var relX = absX - ((ShiftWidth * MetaTileSize) << TileBitShift);
var relY = absY - ((ShiftHeight * MetaTileSize) << TileBitShift);
return (relX, relY);
}
///
/// Determines whether the specified absolute X and Y coordinates are within the valid bounds of the map.
///
/// The absolute X coordinate to validate. Must be within the horizontal bounds of the map.
/// The absolute Y coordinate to validate. Must be within the vertical bounds of the map.
/// if the coordinates are valid; otherwise, .
public bool IsCoordinateValidRelative(int relX, int relY)
{
if ((uint)relX >= CountWidth << TileBitShift)
return false;
if ((uint)relY >= CountHeight << TileBitShift)
return false;
return true;
}
///
/// Layer total width in tiles.
///
public int LayerTotalWidth => CountWidth * TilesPerAcre;
///
/// Layer total height in tiles.
///
public int LayerTotalHeight => CountHeight * TilesPerAcre;
///
/// Gets the total width of the map, in tiles.
///
public int MapTotalWidth => MapAcreWidth * TilesPerAcre;
///
/// Gets the total height of the map, in tiles.
///
public int MapTotalHeight => MapAcreHeight * TilesPerAcre;
}