• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ParadoxGameConverters / ImperatorToCK3 / 18809065839

25 Oct 2025 10:05PM UTC coverage: 49.07%. First build
18809065839

Pull #2796

github

web-flow
Merge 5c2edded3 into 6e3dbca80
Pull Request #2796: Handle a province not being defined when checking if it's land

2414 of 5783 branches covered (41.74%)

Branch coverage included in aggregate %.

22 of 30 new or added lines in 1 file covered. (73.33%)

8639 of 16742 relevant lines covered (51.6%)

4155.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.23
/ImperatorToCK3/CommonUtils/Map/MapData.cs
1
using commonItems;
2
using commonItems.Mods;
3
using Microsoft.VisualBasic.FileIO;
4
using SixLabors.ImageSharp;
5
using SixLabors.ImageSharp.PixelFormats;
6
using System;
7
using System.Collections.Frozen;
8
using System.Collections.Generic;
9
using System.IO;
10
using System.Linq;
11
using System.Runtime.InteropServices;
12

13
namespace ImperatorToCK3.CommonUtils.Map;
14

15
internal sealed class MapData {
16
        [StructLayout(LayoutKind.Auto)]
17
        private readonly struct Point(int x, int y) : IEquatable<Point> {
18
                public int X { get; } = x;
1,757,022✔
19
                public int Y { get; } = y;
1,757,022✔
20

21
                public readonly bool Equals(Point other) {
×
22
                        return X == other.X && Y == other.Y;
×
23
                }
×
24

25
                public override readonly bool Equals(object? obj) {
×
26
                        return obj is Point point && Equals(point);
×
27
                }
×
28

29
                public override readonly int GetHashCode() {
×
30
                        return HashCode.Combine(X, Y);
×
31
                }
×
32
        }
33

34
        private Dictionary<ulong, HashSet<ulong>> NeighborsDict { get; } = [];
22,421✔
35
        private readonly Dictionary<ulong, ProvincePosition> provincePositions = [];
51✔
36
        public IReadOnlyDictionary<ulong, ProvincePosition> ProvincePositions => provincePositions;
×
37
        public ProvinceDefinitions ProvinceDefinitions { get; } = [];
5,296✔
38

39
        private readonly Dictionary<ulong, HashSet<ulong>> provinceAdjacencies = [];
51✔
40
        private readonly Dictionary<ulong, ulong> waterBodiesDict = []; // <province ID, water body ID>
51✔
41

42
        private readonly string[] nonColorableImpassableProvinceTypes = ["wasteland"];
51✔
43
        private readonly string[] colorableImpassableProvinceTypes = ["impassable_mountains", "impassable_terrain"];
51✔
44
        private readonly string[] uninhabitableProvinceTypes = ["uninhabitable"];
51✔
45
        private readonly string[] staticWaterProvinceTypes = ["sea_zones", "lakes", "LAKES", "impassable_seas"];
51✔
46
        private readonly string[] riverProvinceTypes = ["river_provinces"];
51✔
47

48
        private readonly HashSet<ulong> mapEdgeProvinces = [];
51✔
49

50
        private string provincesMapFilename = "provinces.png";
51✔
51
        private string adjacenciesFilename = "adjacencies.csv";
51✔
52

53
        public MapData(ModFilesystem modFS) {
102✔
54
                ParseDefaultMap(modFS);
51✔
55

56
                Logger.Info("Loading province positions...");
51✔
57
                DetermineProvincePositions(modFS);
51✔
58
                Logger.IncrementProgress();
51✔
59

60
                Logger.Info("Loading province adjacencies...");
51✔
61
                LoadAdjacencies(adjacenciesFilename, modFS);
51✔
62

63
                DetermineMapEdgeProvinces(modFS);
51✔
64

65
                Logger.Info("Determining province neighbors...");
51✔
66
                var provincesMapPath = GetProvincesMapPath(modFS);
51✔
67
                if (provincesMapPath is not null) {
59✔
68
                        using Image<Rgb24> provincesMap = Image.Load<Rgb24>(provincesMapPath);
8✔
69
                        DetermineNeighbors(provincesMap, ProvinceDefinitions);
8✔
70
                }
8✔
71

72
                GroupStaticWaterProvinces();
51✔
73

74
                Logger.IncrementProgress();
51✔
75
        }
51✔
76

77
        private void ParseDefaultMap(ModFilesystem modFS) {
51✔
78
                Logger.Info("Loading default map data...");
51✔
79
                const string defaultMapPath = "map_data/default.map";
80
                var defaultMapParser = new Parser();
51✔
81
                defaultMapParser.RegisterKeyword("definitions", reader => {
61✔
82
                        string definitionsFilename = reader.GetString();
10✔
83

51✔
84
                        Logger.Info("Loading province definitions...");
10✔
85
                        ProvinceDefinitions.LoadDefinitions(definitionsFilename, modFS);
10✔
86
                        Logger.IncrementProgress();
10✔
87
                });
61✔
88
                defaultMapParser.RegisterKeyword("provinces", reader => provincesMapFilename = reader.GetString());
59✔
89
                defaultMapParser.RegisterKeyword("rivers", ParserHelpers.IgnoreItem);
51✔
90
                defaultMapParser.RegisterKeyword("topology", ParserHelpers.IgnoreItem);
51✔
91
                defaultMapParser.RegisterKeyword("terrain", ParserHelpers.IgnoreItem);
51✔
92
                defaultMapParser.RegisterKeyword("adjacencies", reader => adjacenciesFilename = reader.GetString());
59✔
93
                defaultMapParser.RegisterKeyword("island_region", ParserHelpers.IgnoreItem);
51✔
94
                defaultMapParser.RegisterKeyword("seasons", ParserHelpers.IgnoreItem);
51✔
95
                defaultMapParser.RegisterKeyword("positions", ParserHelpers.IgnoreItem);
51✔
96
                defaultMapParser.RegisterKeyword("ports", ParserHelpers.IgnoreItem);
51✔
97
                defaultMapParser.RegisterKeyword("climate", ParserHelpers.IgnoreItem);
51✔
98

99
                Dictionary<IEnumerable<string>, SpecialProvinceCategory> provinceTypeToCategoryDict = new() {
51✔
100
                        {nonColorableImpassableProvinceTypes, SpecialProvinceCategory.NonColorableImpassable},
51✔
101
                        {colorableImpassableProvinceTypes, SpecialProvinceCategory.ColorableImpassable},
51✔
102
                        {uninhabitableProvinceTypes, SpecialProvinceCategory.Uninhabitable},
51✔
103
                        {staticWaterProvinceTypes, SpecialProvinceCategory.StaticWater},
51✔
104
                        {riverProvinceTypes, SpecialProvinceCategory.River},
51✔
105
                };
51✔
106
                foreach (var (provTypes, category) in provinceTypeToCategoryDict) {
918✔
107
                        foreach (var provType in provTypes) {
2,142✔
108
                                defaultMapParser.RegisterKeyword(provType, reader => {
640✔
109
                                        Parser.GetNextTokenWithoutMatching(reader); // equals sign
181✔
110
                                        AddProvincesToCategory(category, reader);
181✔
111
                                });
640✔
112
                        }
459✔
113
                }
255✔
114

115
                defaultMapParser.IgnoreAndLogUnregisteredItems();
51✔
116
                defaultMapParser.ParseGameFile(defaultMapPath, modFS);
51✔
117
                Logger.IncrementProgress();
51✔
118
        }
51✔
119

120
        private void GroupStaticWaterProvinces() {
51✔
121
                Logger.Debug("Grouping static water provinces into water bodies...");
51✔
122

123
                var staticWaterProvinces = ProvinceDefinitions
51✔
124
                        .Where(p => p.IsStaticWater)
67,622✔
125
                        .Select(p => p.Id)
114✔
126
                        .ToFrozenSet();
51✔
127

128
                var provinceGroups = new List<HashSet<ulong>>();
51✔
129
                foreach (var provinceId in staticWaterProvinces) {
495✔
130
                        var added = false;
114✔
131
                        List<HashSet<ulong>> connectedGroups = [];
114✔
132

133
                        foreach (var group in provinceGroups) {
2,715✔
134
                                if (group.Any(p => NeighborsDict.TryGetValue(p, out var neighborIds) && neighborIds.Contains(provinceId))) {
1,638✔
135
                                        group.Add(provinceId);
7✔
136
                                        connectedGroups.Add(group);
7✔
137

138
                                        added = true;
7✔
139
                                }
7✔
140
                        }
791✔
141

142
                        // If the province belongs to multiple groups, merge them.
143
                        if (connectedGroups.Count > 1) {
114!
144
                                var mergedGroup = new HashSet<ulong>();
×
145
                                foreach (var group in connectedGroups) {
×
146
                                        mergedGroup.UnionWith(group);
×
147
                                        provinceGroups.Remove(group);
×
148
                                }
×
149
                                mergedGroup.Add(provinceId);
×
150
                                provinceGroups.Add(mergedGroup);
×
151
                        }
×
152

153
                        if (!added) {
221✔
154
                                provinceGroups.Add([provinceId]);
107✔
155
                        }
107✔
156
                }
114✔
157

158
                // Create a dictionary for quick lookup of water body by province.
159
                // Use the lowest province ID in each group as the water body ID.
160
                foreach (var group in provinceGroups) {
474✔
161
                        var waterBodyId = group.Min();
107✔
162
                        foreach (var provinceId in group) {
663✔
163
                                waterBodiesDict[provinceId] = waterBodyId;
114✔
164
                        }
114✔
165
                }
107✔
166
        }
51✔
167

168
        private string? GetProvincesMapPath(ModFilesystem modFS) {
102✔
169
                var relativeMapPath = Path.Join("map_data", provincesMapFilename);
102✔
170
                var provincesMapPath = modFS.GetActualFileLocation(relativeMapPath);
102✔
171
                if (provincesMapPath is not null) {
118✔
172
                        return provincesMapPath;
16✔
173
                }
174

175
                Logger.Warn($"{nameof(provincesMapPath)} not found!");
86✔
176
                return null;
86✔
177
        }
102✔
178

179
        public double GetDistanceBetweenProvinces(ulong province1, ulong province2) {
×
180
                if (!ProvincePositions.TryGetValue(province1, out var province1Position)) {
×
181
                        Logger.Warn($"Province {province1} has no position defined!");
×
182
                        return 0;
×
183
                }
184

185
                if (!ProvincePositions.TryGetValue(province2, out var province2Position)) {
×
186
                        Logger.Warn($"Province {province2} has no position defined!");
×
187
                        return 0;
×
188
                }
189

190
                var xDiff = province1Position.X - province2Position.X;
×
191
                var yDiff = province1Position.Y - province2Position.Y;
×
192
                return Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff));
×
193
        }
×
194

195
        public IReadOnlySet<ulong> GetNeighborProvinceIds(ulong provinceId) {
5✔
196
                return NeighborsDict.TryGetValue(provinceId, out var neighbors) ? neighbors : [];
5✔
197
        }
5✔
198

199
        public bool IsColorableImpassable(ulong provinceId) {
223✔
200
                if (ProvinceDefinitions.TryGetValue(provinceId, out var province)) {
223!
NEW
201
                        return province.IsColorableImpassable;
×
202
                }
203

204
                Logger.Warn($"Province {provinceId} has no definition!");
223✔
205
                return false;
223✔
206
        }
223✔
207

208
        public bool IsImpassable(ulong provinceId) {
3✔
209
                if (ProvinceDefinitions.TryGetValue(provinceId, out var province)) {
3!
NEW
210
                        return province.IsImpassable;
×
211
                }
212

213
                Logger.Warn($"Province {provinceId} has no definition!");
3✔
214
                return false;
3✔
215
        }
3✔
216

217
        private bool IsStaticWater(ulong provinceId) {
23✔
218
                if (ProvinceDefinitions.TryGetValue(provinceId, out var province)) {
46!
219
                        return province.IsStaticWater;
23✔
220
                }
221

NEW
222
                Logger.Warn($"Province {provinceId} has no definition!");
×
NEW
223
                return false;
×
224
        }
23✔
225

226
        private bool IsRiver(ulong provinceId) {
11✔
227
                if (ProvinceDefinitions.TryGetValue(provinceId, out var province)) {
22!
228
                        return province.IsRiver;
11✔
229
                }
230

NEW
231
                Logger.Warn($"Province {provinceId} has no definition!");
×
NEW
232
                return false;
×
233
        }
11✔
234

235
        internal bool IsLand(ulong provinceId) {
6✔
236
                if (ProvinceDefinitions.TryGetValue(provinceId, out var province)) {
12!
237
                        return province.IsLand;
6✔
238
                }
239

NEW
240
                Logger.Warn($"Province {provinceId} has no definition!");
×
NEW
241
                return false;
×
242
        }
6✔
243

244
        public FrozenSet<ulong> ColorableImpassableProvinceIds => ProvinceDefinitions
×
245
                .Where(p => p.IsColorableImpassable).Select(p => p.Id)
×
246
                .ToFrozenSet();
×
247

248
        public IReadOnlySet<ulong> MapEdgeProvinceIds => mapEdgeProvinces;
×
249

250
        private void DetermineProvincePositions(ModFilesystem modFS) {
51✔
251
                const string provincePositionsPath = "gfx/map/map_object_data/building_locators.txt";
252
                var fileParser = new Parser();
51✔
253
                fileParser.RegisterKeyword("game_object_locator", reader => {
51✔
254
                        var listParser = new Parser();
×
255
                        listParser.RegisterKeyword("instances", instancesReader => {
×
256
                                foreach (var blob in new BlobList(instancesReader).Blobs) {
×
257
                                        var blobReader = new BufferedReader(blob);
×
258
                                        var instance = ProvincePosition.Parse(blobReader);
×
259
                                        provincePositions[instance.Id] = instance;
×
260
                                }
×
261
                        });
×
262
                        listParser.IgnoreUnregisteredItems();
×
263
                        listParser.ParseStream(reader);
×
264
                });
51✔
265
                fileParser.IgnoreUnregisteredItems();
51✔
266
                fileParser.ParseGameFile(provincePositionsPath, modFS);
51✔
267
        }
51✔
268

269
        private void DetermineNeighbors(Image<Rgb24> provincesMap, ProvinceDefinitions provinceDefinitions) {
8✔
270
                var height = provincesMap.Height;
8✔
271
                var width = provincesMap.Width;
8✔
272
                for (var y = 0; y < height; ++y) {
2,224✔
273
                        for (var x = 0; x < width; ++x) {
376,325✔
274
                                var position = new Point(x, y);
124,951✔
275

276
                                var centerColor = GetCenterColor(position, provincesMap);
124,951✔
277
                                var aboveColor = GetAboveColor(position, provincesMap);
124,951✔
278
                                var belowColor = GetBelowColor(position, height, provincesMap);
124,951✔
279
                                var leftColor = GetLeftColor(position, provincesMap);
124,951✔
280
                                var rightColor = GetRightColor(position, width, provincesMap);
124,951✔
281

282
                                if (!centerColor.Equals(aboveColor)) {
131,216✔
283
                                        HandleNeighbor(centerColor, aboveColor, provinceDefinitions);
6,265✔
284
                                }
6,265✔
285

286
                                if (!centerColor.Equals(rightColor)) {
129,550✔
287
                                        HandleNeighbor(centerColor, rightColor, provinceDefinitions);
4,599✔
288
                                }
4,599✔
289

290
                                if (!centerColor.Equals(belowColor)) {
131,216✔
291
                                        HandleNeighbor(centerColor, belowColor, provinceDefinitions);
6,265✔
292
                                }
6,265✔
293

294
                                if (!centerColor.Equals(leftColor)) {
129,550✔
295
                                        HandleNeighbor(centerColor, leftColor, provinceDefinitions);
4,599✔
296
                                }
4,599✔
297
                        }
124,951✔
298
                }
736✔
299
        }
8✔
300

301
        private void AddProvincesToCategory(SpecialProvinceCategory category, BufferedReader provincesGroupReader) {
181✔
302
                var typeOfGroup = Parser.GetNextTokenWithoutMatching(provincesGroupReader);
181✔
303
                var provIds = provincesGroupReader.GetULongs();
181✔
304

305
                if (typeOfGroup == "RANGE") {
304✔
306
                        if (provIds.Count is < 1 or > 2) {
123!
307
                                Logger.Warn($"A range of provinces should have 1 or 2 elements, got: {string.Join(", ", provIds)}");
×
308
                        }
×
309
                        if (provIds.Count == 0) {
123!
310
                                return;
×
311
                        }
312

313
                        var beginning = provIds[0];
123✔
314
                        var end = provIds[^1];
123✔
315
                        for (var id = beginning; id <= end; ++id) {
3,177✔
316
                                if (ProvinceDefinitions.TryGetValue(id, out var province)) {
1,089✔
317
                                        province.AddSpecialCategory(category);
112✔
318
                                }
112✔
319
                        }
977✔
320
                } else {
181✔
321
                        foreach (var p in ProvinceDefinitions.Where(p => provIds.Contains(p.Id))) {
360✔
322
                                p.AddSpecialCategory(category);
4✔
323
                        }
4✔
324
                }
58✔
325
        }
181✔
326

327
        private static Rgb24 GetCenterColor(Point position, Image<Rgb24> provincesMap) {
124,951✔
328
                return GetPixelColor(position, provincesMap);
124,951✔
329
        }
124,951✔
330

331
        private static Rgb24 GetAboveColor(Point position, Image<Rgb24> provincesMap) {
124,951✔
332
                int y = position.Y;
124,951✔
333
                if (y > 0) {
248,711✔
334
                        --y;
123,760✔
335
                }
123,760✔
336

337
                return GetPixelColor(new(position.X, y), provincesMap);
124,951✔
338
        }
124,951✔
339

340
        private static Rgb24 GetBelowColor(Point position, int height, Image<Rgb24> provincesMap) {
124,951✔
341
                int y = position.Y;
124,951✔
342
                if (y < height - 1) {
248,711✔
343
                        ++y;
123,760✔
344
                }
123,760✔
345

346
                return GetPixelColor(new(position.X, y), provincesMap);
124,951✔
347
        }
124,951✔
348

349
        private static Rgb24 GetLeftColor(Point position, Image<Rgb24> provincesMap) {
124,951✔
350
                int x = position.X;
124,951✔
351
                if (x > 0) {
249,166✔
352
                        --x;
124,215✔
353
                }
124,215✔
354

355
                return GetPixelColor(new(x, position.Y), provincesMap);
124,951✔
356
        }
124,951✔
357

358
        private static Rgb24 GetRightColor(Point position, int width, Image<Rgb24> provincesMap) {
124,951✔
359
                int x = position.X;
124,951✔
360
                if (x < width - 1) {
249,166✔
361
                        ++x;
124,215✔
362
                }
124,215✔
363

364
                return GetPixelColor(new(x, position.Y), provincesMap);
124,951✔
365
        }
124,951✔
366

367
        private static Rgb24 GetPixelColor(Point position, Image<Rgb24> provincesMap) {
628,609✔
368
                return provincesMap[position.X, position.Y];
628,609✔
369
        }
628,609✔
370

371
        private void HandleNeighbor(
372
                Rgb24 centerColor,
373
                Rgb24 otherColor,
374
                ProvinceDefinitions provinceDefinitions
375
        ) {
21,728✔
376
                if (!provinceDefinitions.ColorToProvinceDict.TryGetValue(centerColor, out ulong centerProvince)) {
21,924✔
377
                        Logger.Warn($"Province not found for color {centerColor}!");
196✔
378
                        return;
196✔
379
                }
380

381
                if (!provinceDefinitions.ColorToProvinceDict.TryGetValue(otherColor, out ulong otherProvince)) {
21,728✔
382
                        Logger.Warn($"Province not found for color {otherColor}!");
196✔
383
                        return;
196✔
384
                }
385

386
                AddNeighbor(centerProvince, otherProvince);
21,336✔
387
        }
21,728✔
388

389
        private void AddNeighbor(ulong mainProvince, ulong neighborProvince) {
21,336✔
390
                if (NeighborsDict.TryGetValue(mainProvince, out var neighbors)) {
42,497✔
391
                        neighbors.Add(neighborProvince);
21,161✔
392
                } else {
21,336✔
393
                        NeighborsDict[mainProvince] = [neighborProvince];
175✔
394
                }
175✔
395
        }
21,336✔
396

397
        /// Function for checking if two provinces are directly neighboring or border the same static water body.
398
        public bool AreProvinceGroupsAdjacent(FrozenSet<ulong> group1, FrozenSet<ulong> group2) {
5✔
399
                return AreProvinceGroupsAdjacentByLand(group1, group2) || AreProvinceGroupsConnectedByWaterBody(group1, group2);
5✔
400
        }
5✔
401

402
        public bool AreProvinceGroupsAdjacentByLand(FrozenSet<ulong> group1, FrozenSet<ulong> group2) {
5✔
403
                var group1Neighbors = new HashSet<ulong>();
5✔
404
                foreach (var province in group1) {
30✔
405
                        if (NeighborsDict.TryGetValue(province, out var neighbors)) {
10✔
406
                                group1Neighbors.UnionWith(neighbors);
5✔
407
                        }
5✔
408
                }
5✔
409
                if (group1Neighbors.Overlaps(group2)) {
6✔
410
                        return true;
1✔
411
                }
412

413
                var group1Adjacencies = new HashSet<ulong>();
4✔
414
                foreach (var province in group1) {
24✔
415
                        if (provinceAdjacencies.TryGetValue(province, out var adjacencies)) {
7✔
416
                                group1Adjacencies.UnionWith(adjacencies);
3✔
417
                        }
3✔
418
                }
4✔
419
                if (group1Adjacencies.Overlaps(group2)) {
5✔
420
                        return true;
1✔
421
                }
422

423
                var group2RiverProvinceNeighbors = group2
3✔
424
                        .SelectMany(provId => NeighborsDict.TryGetValue(provId, out var neighbors) ? neighbors : [])
3✔
425
                        .Where(IsRiver)
3✔
426
                        .ToFrozenSet();
3✔
427
                if (group1Neighbors.Overlaps(group2RiverProvinceNeighbors)) {
3!
428
                        return true;
×
429
                }
430

431
                return false;
3✔
432
        }
5✔
433

434
        // Function for checking if two land provinces are connected to the same water body.
435
        public bool AreProvinceGroupsConnectedByWaterBody(FrozenSet<ulong> group1, FrozenSet<ulong> group2) {
3✔
436
                var group1WaterNeighbors = new HashSet<ulong>();
3✔
437
                foreach (var provId in group1) {
18✔
438
                        if (!NeighborsDict.TryGetValue(provId, out var neighbors)) {
3!
439
                                continue;
×
440
                        }
441
                        foreach (ulong neighbor in neighbors.Where(IsStaticWater)) {
27✔
442
                                group1WaterNeighbors.Add(neighbor);
6✔
443
                        }
6✔
444
                }
3✔
445
                if (group1WaterNeighbors.Count == 0) {
3!
446
                        return false;
×
447
                }
448

449
                var group2WaterNeighbors = group2
3✔
450
                        .SelectMany(provId => NeighborsDict.TryGetValue(provId, out var neighbors) ? neighbors : [])
3!
451
                        .Where(IsStaticWater)
3✔
452
                        .ToFrozenSet();
3✔
453
                if (group2WaterNeighbors.Count == 0) {
4✔
454
                        return false;
1✔
455
                }
456

457
                var group1WaterBodies = group1WaterNeighbors.Select(id => waterBodiesDict[id]).ToFrozenSet();
6✔
458

459
                return group2WaterNeighbors
2✔
460
                        .Any(group2ProvId => group1WaterBodies.Contains(waterBodiesDict[group2ProvId]));
4✔
461
        }
3✔
462

463
        private void LoadAdjacencies(string adjacenciesFilename, ModFilesystem modFS) {
51✔
464
                var adjacenciesPath = modFS.GetActualFileLocation(Path.Join("map_data", adjacenciesFilename));
51✔
465
                if (adjacenciesPath is null) {
94✔
466
                        Logger.Warn($"Adjacencies file {adjacenciesFilename} not found!");
43✔
467
                        return;
43✔
468
                }
469
                Logger.Debug($"Loading adjacencies from \"{adjacenciesPath}\"...");
8✔
470

471
                int count = 0;
8✔
472
                using (var parser = new TextFieldParser(adjacenciesPath)) {
16✔
473
                        parser.TextFieldType = FieldType.Delimited;
8✔
474
                        parser.SetDelimiters(";");
8✔
475
                        parser.CommentTokens = ["#"];
8✔
476
                        parser.TrimWhiteSpace = true;
8✔
477

478
                        // Skip the header row.
479
                        parser.ReadFields();
8✔
480

481
                        while (!parser.EndOfData) {
438✔
482
                                string[]? fields = parser.ReadFields();
215✔
483
                                if (fields is null) {
215!
484
                                        continue;
×
485
                                }
486

487
                                if (fields.Length < 2) {
215!
488
                                        continue;
×
489
                                }
490

491
                                var fromStr = fields[0];
215✔
492
                                if (fromStr == "-1") {
216✔
493
                                        continue;
1✔
494
                                }
495

496
                                var toStr = fields[1];
214✔
497
                                if (toStr == "-1") {
214!
498
                                        continue;
×
499
                                }
500

501
                                if (!ulong.TryParse(fromStr, out var from) || !ulong.TryParse(toStr, out var to)) {
214!
502
                                        continue;
×
503
                                }
504

505
                                AddAdjacency(from, to);
214✔
506
                                ++count;
214✔
507
                        }
214✔
508
                }
8✔
509
                Logger.Debug($"Loaded {count} province adjacencies.");
8✔
510
        }
51✔
511

512
        private void AddAdjacency(ulong province1, ulong province2) {
214✔
513
                if (!provinceAdjacencies.TryGetValue(province1, out var adjacencies)) {
400✔
514
                        adjacencies = [];
186✔
515
                        provinceAdjacencies[province1] = adjacencies;
186✔
516
                }
186✔
517
                adjacencies.Add(province2);
214✔
518

519
                // Since adjacency is bidirectional, add the reverse adjacency as well
520
                if (!provinceAdjacencies.TryGetValue(province2, out adjacencies)) {
410✔
521
                        adjacencies = [];
196✔
522
                        provinceAdjacencies[province2] = adjacencies;
196✔
523
                }
196✔
524
                adjacencies.Add(province1);
214✔
525
        }
214✔
526

527
        private void DetermineMapEdgeProvinces(ModFilesystem modFS) {
51✔
528
                Logger.Debug("Determining map edge provinces...");
51✔
529

530
                var mapPath = GetProvincesMapPath(modFS);
51✔
531
                if (mapPath is null) {
94✔
532
                        return;
43✔
533
                }
534

535
                using var mapPng = Image.Load<Rgb24>(mapPath);
8✔
536
                var height = mapPng.Height;
8✔
537
                var width = mapPng.Width;
8✔
538

539
                for (var y = 0; y < height; ++y) {
2,224✔
540
                        // Get left edge color.
541
                        var color = GetPixelColor(new Point(0, y), mapPng);
736✔
542
                        if (ProvinceDefinitions.ColorToProvinceDict.TryGetValue(color, out var provinceId)) {
1,472!
543
                                mapEdgeProvinces.Add(provinceId);
736✔
544
                        } else {
736✔
545
                                Logger.Warn($"Province not found for color {color}!");
×
546
                        }
×
547

548
                        // Get right edge color.
549
                        color = GetPixelColor(new Point(width - 1, y), mapPng);
736✔
550
                        if (ProvinceDefinitions.ColorToProvinceDict.TryGetValue(color, out provinceId)) {
1,472!
551
                                mapEdgeProvinces.Add(provinceId);
736✔
552
                        } else {
736✔
553
                                Logger.Warn($"Province not found for color {color}!");
×
554
                        }
×
555
                }
736✔
556

557
                for (var x = 0; x < width; ++x) {
3,589✔
558
                        // Get top edge color.
559
                        var color = GetPixelColor(new Point(x, 0), mapPng);
1,191✔
560
                        if (ProvinceDefinitions.ColorToProvinceDict.TryGetValue(color, out var provinceId)) {
2,382!
561
                                mapEdgeProvinces.Add(provinceId);
1,191✔
562
                        } else {
1,191✔
563
                                Logger.Warn($"Province not found for color {color}!");
×
564
                        }
×
565

566
                        // Get bottom edge color.
567
                        color = GetPixelColor(new Point(x, height - 1), mapPng);
1,191✔
568
                        if (ProvinceDefinitions.ColorToProvinceDict.TryGetValue(color, out provinceId)) {
2,382!
569
                                mapEdgeProvinces.Add(provinceId);
1,191✔
570
                        } else {
1,191✔
571
                                Logger.Warn($"Province not found for color {color}!");
×
572
                        }
×
573
                }
1,191✔
574
        }
59✔
575
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc