2024-04-14 18:50:20 +02:00
|
|
|
using System.IO;
|
2024-04-14 21:10:21 +02:00
|
|
|
using SixLabors.ImageSharp;
|
|
|
|
using SixLabors.ImageSharp.PixelFormats;
|
2024-04-14 18:50:20 +02:00
|
|
|
|
2024-04-10 19:25:45 +02:00
|
|
|
namespace TanksServer.GameLogic;
|
2024-04-06 13:46:34 +02:00
|
|
|
|
2024-04-07 13:02:49 +02:00
|
|
|
internal sealed class MapService
|
2024-04-06 13:46:34 +02:00
|
|
|
{
|
2024-04-12 18:32:10 +02:00
|
|
|
public const ushort TilesPerRow = 44;
|
|
|
|
public const ushort TilesPerColumn = 20;
|
|
|
|
public const ushort TileSize = 8;
|
|
|
|
public const ushort PixelsPerRow = TilesPerRow * TileSize;
|
|
|
|
public const ushort PixelsPerColumn = TilesPerColumn * TileSize;
|
2024-04-14 18:50:20 +02:00
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
private readonly Dictionary<string, bool[,]> _maps = new();
|
2024-04-14 23:11:00 +02:00
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
public IEnumerable<string> MapNames => _maps.Keys;
|
|
|
|
|
|
|
|
public Map Current { get; private set; }
|
2024-04-14 18:50:20 +02:00
|
|
|
|
2024-04-14 21:10:21 +02:00
|
|
|
public MapService()
|
2024-04-14 18:50:20 +02:00
|
|
|
{
|
2024-04-16 18:55:34 +02:00
|
|
|
foreach (var file in Directory.EnumerateFiles("./assets/maps/", "*.txt"))
|
|
|
|
LoadMapString(file);
|
|
|
|
foreach (var file in Directory.EnumerateFiles("./assets/maps/", "*.png"))
|
|
|
|
LoadMapPng(file);
|
|
|
|
|
|
|
|
var chosenMapIndex = Random.Shared.Next(_maps.Count);
|
|
|
|
var chosenMapName = _maps.Keys.Skip(chosenMapIndex).First();
|
|
|
|
Current = new Map(chosenMapName, _maps[chosenMapName]);
|
2024-04-14 21:10:21 +02:00
|
|
|
}
|
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
private void LoadMapPng(string file)
|
2024-04-14 21:10:21 +02:00
|
|
|
{
|
|
|
|
using var image = Image.Load<Rgba32>(file);
|
|
|
|
|
|
|
|
if (image.Width != PixelsPerRow || image.Height != PixelsPerColumn)
|
|
|
|
throw new FileLoadException($"invalid image size in file {file}");
|
|
|
|
|
|
|
|
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
2024-04-14 22:45:51 +02:00
|
|
|
var walls = new bool[PixelsPerRow, PixelsPerColumn];
|
|
|
|
|
2024-04-14 21:10:21 +02:00
|
|
|
for (var y = 0; y < image.Height; y++)
|
|
|
|
for (var x = 0; x < image.Width; x++)
|
2024-04-14 22:45:51 +02:00
|
|
|
walls[x, y] = image[x, y] == whitePixel;
|
2024-04-14 18:50:20 +02:00
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
_maps.Add(Path.GetFileName(file), walls);
|
2024-04-14 18:50:20 +02:00
|
|
|
}
|
2024-04-06 13:46:34 +02:00
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
private void LoadMapString(string file)
|
2024-04-14 18:50:20 +02:00
|
|
|
{
|
2024-04-14 21:10:21 +02:00
|
|
|
var map = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim();
|
|
|
|
if (map.Length != TilesPerColumn * TilesPerRow)
|
|
|
|
throw new FileLoadException($"cannot load map {file}: invalid length");
|
2024-04-06 13:46:34 +02:00
|
|
|
|
2024-04-14 22:45:51 +02:00
|
|
|
var walls = new bool[PixelsPerRow, PixelsPerColumn];
|
2024-04-06 13:46:34 +02:00
|
|
|
|
2024-04-14 22:45:51 +02:00
|
|
|
for (ushort tileX = 0; tileX < TilesPerRow; tileX++)
|
|
|
|
for (ushort tileY = 0; tileY < TilesPerColumn; tileY++)
|
2024-04-14 21:10:21 +02:00
|
|
|
{
|
|
|
|
var tile = new TilePosition(tileX, tileY);
|
|
|
|
if (map[tileX + tileY * TilesPerRow] != '#')
|
|
|
|
continue;
|
|
|
|
|
2024-04-14 22:45:51 +02:00
|
|
|
for (byte pixelInTileX = 0; pixelInTileX < TileSize; pixelInTileX++)
|
|
|
|
for (byte pixelInTileY = 0; pixelInTileY < TileSize; pixelInTileY++)
|
2024-04-14 21:10:21 +02:00
|
|
|
{
|
2024-04-14 22:45:51 +02:00
|
|
|
var (x, y) = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY);
|
|
|
|
walls[x, y] = true;
|
2024-04-14 21:10:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-16 18:55:34 +02:00
|
|
|
_maps.Add(Path.GetFileName(file), walls);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool TrySwitchTo(string name)
|
|
|
|
{
|
|
|
|
if (!_maps.TryGetValue(name, out var mapData))
|
|
|
|
return false;
|
2024-04-19 13:41:03 +02:00
|
|
|
Current = new Map(name, (bool[,]) mapData.Clone());
|
2024-04-16 18:55:34 +02:00
|
|
|
return true;
|
2024-04-14 21:10:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-14 23:11:00 +02:00
|
|
|
internal sealed class Map(string name, bool[,] walls)
|
2024-04-14 21:10:21 +02:00
|
|
|
{
|
2024-04-14 23:11:00 +02:00
|
|
|
public string Name => name;
|
|
|
|
|
2024-04-14 22:45:51 +02:00
|
|
|
public bool IsWall(int x, int y) => walls[x, y];
|
|
|
|
|
|
|
|
public bool IsWall(PixelPosition position) => walls[position.X, position.Y];
|
|
|
|
|
|
|
|
public bool IsWall(TilePosition position)
|
|
|
|
{
|
|
|
|
var pixel = position.ToPixelPosition();
|
|
|
|
|
|
|
|
for (short dx = 1; dx < MapService.TilesPerRow - 1; dx++)
|
|
|
|
for (short dy = 1; dy < MapService.TilesPerColumn - 1; dy++)
|
|
|
|
{
|
|
|
|
if (IsWall(pixel.GetPixelRelative(dx, dy)))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2024-04-16 18:55:34 +02:00
|
|
|
|
2024-04-21 19:34:22 +02:00
|
|
|
public bool TryDestroyWallAt(PixelPosition pixel)
|
|
|
|
{
|
|
|
|
var result = walls[pixel.X, pixel.Y];
|
|
|
|
if (result)
|
|
|
|
walls[pixel.X, pixel.Y] = false;
|
|
|
|
return result;
|
|
|
|
}
|
2024-04-06 13:46:34 +02:00
|
|
|
}
|