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