File size: 4,251 Bytes
5eb163f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 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);
}
|