SilkroadBot / src /SilkroadBot.Navigation /Models /NavigationService.cs
Ahmedramadan24's picture
Add src/SilkroadBot.Navigation/Models/NavigationService.cs
5eb163f verified
using SilkroadBot.Navigation.Interfaces;
namespace SilkroadBot.Navigation.Models;
/// <summary>
/// File-based map data provider. Loads navigation grids from disk.
/// </summary>
public class FileMapDataProvider : IMapDataProvider
{
private readonly string _dataDirectory;
private readonly Dictionary<short, RegionNavData> _cache = new();
private readonly HashSet<short> _availableRegions = new();
public FileMapDataProvider(string dataDirectory)
{
_dataDirectory = dataDirectory;
ScanAvailableRegions();
}
public async Task<RegionNavData?> LoadRegionAsync(short regionId)
{
if (_cache.TryGetValue(regionId, out var cached))
return cached;
var filePath = Path.Combine(_dataDirectory, $"region_{regionId}.nav");
if (!File.Exists(filePath))
{
// Generate a default passable region for testing
return GenerateDefaultRegion(regionId);
}
var data = await LoadFromFileAsync(filePath, regionId);
if (data != null)
_cache[regionId] = data;
return data;
}
public bool HasRegion(short regionId) => _availableRegions.Contains(regionId);
public IReadOnlyList<short> GetAvailableRegions() => _availableRegions.ToList();
private void ScanAvailableRegions()
{
if (!Directory.Exists(_dataDirectory))
{
Directory.CreateDirectory(_dataDirectory);
return;
}
foreach (var file in Directory.GetFiles(_dataDirectory, "region_*.nav"))
{
var name = Path.GetFileNameWithoutExtension(file);
if (short.TryParse(name.Replace("region_", ""), out var id))
_availableRegions.Add(id);
}
}
private static async Task<RegionNavData?> LoadFromFileAsync(string path, short regionId)
{
try
{
var bytes = await File.ReadAllBytesAsync(path);
if (bytes.Length < 8) return null;
int width = BitConverter.ToInt32(bytes, 0);
int height = BitConverter.ToInt32(bytes, 4);
var grid = new byte[width, height];
int offset = 8;
for (int x = 0; x < width && offset < bytes.Length; x++)
for (int y = 0; y < height && offset < bytes.Length; y++)
grid[x, y] = bytes[offset++];
return new RegionNavData
{
RegionId = regionId,
Width = width * 10, // Scale to game units
Height = height * 10,
WalkabilityGrid = grid
};
}
catch
{
return null;
}
}
private static RegionNavData GenerateDefaultRegion(short regionId)
{
const int size = 128;
var grid = new byte[size, size];
// Default: everything is walkable
for (int x = 0; x < size; x++)
for (int y = 0; y < size; y++)
grid[x, y] = 1;
return new RegionNavData
{
RegionId = regionId,
Width = size * 10,
Height = size * 10,
WalkabilityGrid = grid
};
}
}
/// <summary>
/// Navigation service implementation combining pathfinding with map data.
/// </summary>
public class NavigationService : Plugins.SDK.Interfaces.INavigationService
{
private readonly Interfaces.IPathfindingAlgorithm _pathfinder;
public NavigationService(Interfaces.IPathfindingAlgorithm pathfinder)
{
_pathfinder = pathfinder;
}
public async Task<IReadOnlyList<Domain.Models.WorldPosition>> FindPathAsync(
Domain.Models.WorldPosition from,
Domain.Models.WorldPosition to,
CancellationToken ct = default)
{
var result = await _pathfinder.FindPathAsync(from, to);
return result.Success ? result.Waypoints : Array.Empty<Domain.Models.WorldPosition>();
}
public bool IsWalkable(Domain.Models.WorldPosition position)
=> _pathfinder.IsPositionWalkable(position);
public Domain.Models.WorldPosition GetNearestWalkable(Domain.Models.WorldPosition position)
=> _pathfinder.GetNearestWalkablePosition(position);
}