using SilkroadBot.Navigation.Interfaces; namespace SilkroadBot.Navigation.Models; /// /// File-based map data provider. Loads navigation grids from disk. /// public class FileMapDataProvider : IMapDataProvider { private readonly string _dataDirectory; private readonly Dictionary _cache = new(); private readonly HashSet _availableRegions = new(); public FileMapDataProvider(string dataDirectory) { _dataDirectory = dataDirectory; ScanAvailableRegions(); } public async Task 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 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 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 }; } } /// /// Navigation service implementation combining pathfinding with map data. /// public class NavigationService : Plugins.SDK.Interfaces.INavigationService { private readonly Interfaces.IPathfindingAlgorithm _pathfinder; public NavigationService(Interfaces.IPathfindingAlgorithm pathfinder) { _pathfinder = pathfinder; } public async Task> 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(); } public bool IsWalkable(Domain.Models.WorldPosition position) => _pathfinder.IsPositionWalkable(position); public Domain.Models.WorldPosition GetNearestWalkable(Domain.Models.WorldPosition position) => _pathfinder.GetNearestWalkablePosition(position); }