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; }