Skip to content

Commit 9806f5d

Browse files
authored
Detect invalid province mappings (#2772) #minor
This detects land -> water or water -> land province mappings. Also fixed the few Invictus province mappings that were detected as invalid.
1 parent 4b61d6e commit 9806f5d

File tree

11 files changed

+147
-70
lines changed

11 files changed

+147
-70
lines changed

ImperatorToCK3.UnitTests/Mappers/Province/ProvinceMapperTests.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using ImperatorToCK3.Mappers.Province;
1+
using commonItems.Mods;
2+
using ImperatorToCK3.CommonUtils.Map;
3+
using ImperatorToCK3.Mappers.Province;
4+
using System;
25
using System.IO;
36
using Xunit;
47

@@ -8,7 +11,11 @@ namespace ImperatorToCK3.UnitTests.Mappers.Province;
811
[CollectionDefinition("Sequential", DisableParallelization = true)]
912
public class ProvinceMapperTests {
1013
private const string TestFilesPath = "TestFiles/MapperTests/ProvinceMapper";
11-
14+
private const string ImperatorRoot = "TestFiles/MapperTests/ProvinceMapper/Imperator/game";
15+
private const string CK3Root = "TestFiles/MapperTests/ProvinceMapper/CK3/game";
16+
private static readonly ModFilesystem IRModFS = new(ImperatorRoot, []);
17+
private static readonly ModFilesystem CK3ModFS = new(CK3Root, []);
18+
1219
[Fact]
1320
public void EmptyMappingsDefaultToEmpty() {
1421
var mapper = new ProvinceMapper();
@@ -45,4 +52,23 @@ public void CanLookupCK3Provinces() {
4552
Assert.Equal((ulong)2, mapper.GetCK3ProvinceNumbers(2)[0]);
4653
Assert.Equal((ulong)1, mapper.GetCK3ProvinceNumbers(2)[1]);
4754
}
55+
56+
[Fact]
57+
public void TypeMismatchesAreDetected() {
58+
var mapper = new ProvinceMapper();
59+
var mappingsPath = Path.Combine(TestFilesPath, "type_mismatches.txt");
60+
mapper.LoadMappings(mappingsPath);
61+
62+
var irMapData = new MapData(IRModFS);
63+
var ck3MapData = new MapData(CK3ModFS);
64+
65+
var output = new StringWriter();
66+
Console.SetOut(output);
67+
68+
mapper.DetectInvalidMappings(irMapData, ck3MapData);
69+
string log = output.ToString();
70+
Assert.Contains("I:R land province 1 is mapped to CK3 water province 2! Fix the province mappings!", log);
71+
Assert.Contains("I:R water province 2 is mapped to CK3 land province 1! Fix the province mappings!", log);
72+
Assert.DoesNotContain(" 3 is mapped", log); // Mapping 3 -> 3 has land on both ends, so should not be reported.
73+
}
4874
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
definitions = definition.csv
2+
uninhabitable = LIST { 1}
3+
sea_zones = LIST { 2 }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0;0;0;0;x;x;
2+
1;42;3;128;VESTFIRDIR;x;
3+
2;84;6;1;REYKJAVIK;x;
4+
3;126;9;2;AKUREYRI;x;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
definitions = definition.csv
2+
uninhabitable = LIST { 1}
3+
sea_zones = LIST { 2 }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0;0;0;0;x;x;
2+
1;42;3;128;VESTFIRDIR;x;
3+
2;84;6;1;REYKJAVIK;x;
4+
3;126;9;2;AKUREYRI;x;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
test_version = {
2+
link = { imp = 1 ck3 = 2 } # land to water
3+
link = { imp = 2 ck3 = 1 } # water to land
4+
link = { imp = 3 ck3 = 3 } # land to land, OK
5+
}

ImperatorToCK3/CK3/World.cs

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
7575
Logger.Info("*** Hello CK3, let's get painting. ***");
7676

7777
warMapper.DetectUnmappedWarGoals(impWorld.ModFS);
78-
78+
7979
DetermineCK3Dlcs(config);
8080
LoadAndDetectCK3Mods(config);
8181

@@ -90,14 +90,14 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
9090
WorldOutputter.CreateModFolder(outputModPath);
9191
// This will also convert all Liquid templates into simple text files.
9292
WorldOutputter.CopyBlankModFilesToOutput(outputModPath, config.GetCK3ModFlags());
93-
93+
9494
// Include a fake mod pointing to blankMod in the output folder.
9595
LoadedMods.Add(new Mod("blankMod", outputModPath));
9696
ModFS = new ModFilesystem(Path.Combine(config.CK3Path, "game"), LoadedMods);
9797

9898
var ck3Defines = new Defines();
9999
ck3Defines.LoadDefines(ModFS);
100-
100+
101101
ColorFactory ck3ColorFactory = new();
102102
// Now that we have the mod filesystem, we can initialize the localization database.
103103
Parallel.Invoke(
@@ -121,10 +121,11 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
121121
FileTweaker.ModifyAndRemovePartsOfFiles(ModFS, outputModPath, config).Wait();
122122
}
123123
);
124-
124+
125125
System.Collections.Generic.OrderedDictionary<string, bool> ck3ModFlags = config.GetCK3ModFlags();
126-
126+
127127
Parallel.Invoke(
128+
() => provinceMapper.DetectInvalidMappings(impWorld.MapData, MapData), // depends on ProvinceMapper and MapData
128129
() => { // depends on ck3ColorFactory and CulturalPillars
129130
// Load CK3 cultures from CK3 mod filesystem.
130131
Logger.Info("Loading cultural pillars...");
@@ -142,9 +143,9 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
142143
},
143144
() => LoadMenAtArmsTypes(ModFS, ScriptValues), // depends on ScriptValues
144145
() => { // depends on LocDB and CK3CoaMapper
145-
// Load vanilla CK3 landed titles and their history
146+
// Load vanilla CK3 landed titles and their history
146147
LandedTitles.LoadTitles(ModFS, LocDB, ck3ColorFactory);
147-
148+
148149
if (config.StaticDeJure) {
149150
Logger.Info("Setting static de jure kingdoms and empires...");
150151

@@ -154,25 +155,25 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
154155

155156
Logger.IncrementProgress();
156157
}
157-
158+
158159
LandedTitles.SetCoatsOfArms(CK3CoaMapper);
159-
160+
160161
LandedTitles.LoadHistory(config, ModFS);
161162
LandedTitles.LoadCulturalNamesFromConfigurables();
162163
}
163164
);
164-
165+
165166
// Load regions.
166167
ck3RegionMapper = new CK3RegionMapper(ModFS, LandedTitles);
167168
imperatorRegionMapper = impWorld.ImperatorRegionMapper;
168-
169+
169170
CultureMapper cultureMapper = null!;
170171
TraitMapper traitMapper = null!;
171172
DNAFactory dnaFactory = null!;
172173
Parallel.Invoke(
173174
() => { // depends on ck3ColorFactory and landed titles being loaded
174-
// Load CK3 religions from game and blankMod.
175-
// Holy sites need to be loaded after landed titles.
175+
// Load CK3 religions from game and blankMod.
176+
// Holy sites need to be loaded after landed titles.
176177
Religions.LoadDoctrines(ModFS);
177178
Logger.Info("Loaded CK3 doctrines.");
178179
Religions.LoadConverterHolySites("configurables/converter_holy_sites.txt");
@@ -190,15 +191,15 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
190191
Religions.RemoveChristianAndIslamicSyncretismFromAllFaiths();
191192
// Now that all the faiths are loaded, remove liege entries from the history of religious head titles.
192193
LandedTitles.RemoveLiegeEntriesFromReligiousHeadHistory(Religions);
193-
194+
194195
Religions.LoadReplaceableHolySites("configurables/replaceable_holy_sites.txt");
195196
Logger.Info("Loaded replaceable holy sites.");
196197
},
197-
198+
198199
() => cultureMapper = new CultureMapper(imperatorRegionMapper, ck3RegionMapper, Cultures),
199-
200+
200201
() => traitMapper = new("configurables/trait_map.txt", ModFS),
201-
202+
202203
() => {
203204
Logger.Info("Initializing DNA factory...");
204205
dnaFactory = new(impWorld.ModFS, ModFS);
@@ -210,14 +211,14 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
210211
Logger.IncrementProgress();
211212
}
212213
);
213-
214+
214215
var religionMapper = new ReligionMapper(Religions, imperatorRegionMapper, ck3RegionMapper);
215-
216+
216217
Parallel.Invoke(
217218
() => Cultures.ImportTechnology(impWorld.Countries, cultureMapper, provinceMapper, impWorld.InventionsDB, impWorld.LocDB, ck3ModFlags),
218-
219+
219220
() => { // depends on religionMapper
220-
// Check if all I:R religions have a base mapping.
221+
// Check if all I:R religions have a base mapping.
221222
foreach (var irReligionId in impWorld.Religions.Select(r => r.Id)) {
222223
var baseMapping = religionMapper.Match(irReligionId, null, null, null, null, config);
223224
if (baseMapping is null) {
@@ -231,25 +232,25 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
231232
}
232233
},
233234
() => { // depends on cultureMapper
234-
// Check if all I:R cultures have a base mapping.
235+
// Check if all I:R cultures have a base mapping.
235236
var irCultureIds = impWorld.CulturesDB.SelectMany(g => g.Select(c => c.Id));
236237
foreach (var irCultureId in irCultureIds) {
237-
var baseMapping = cultureMapper.Match(irCultureId, null, null, null);
238-
if (baseMapping is null) {
238+
var baseMapping = cultureMapper.Match(irCultureId, null, null, null);
239+
if (baseMapping is null) {
239240
string cultureStr = "ID: " + irCultureId;
240241
var localizedName = impWorld.LocDB.GetLocBlockForKey(irCultureId)?["english"];
241242
if (localizedName is not null) {
242243
cultureStr += $", name: {localizedName}";
243244
}
244-
Logger.Warn($"No base mapping found for I:R culture {cultureStr}!");
245-
}
246-
}
245+
Logger.Warn($"No base mapping found for I:R culture {cultureStr}!");
246+
}
247+
}
247248
},
248249
() => { // depends on TraitMapper and CK3 characters being loaded
249250
Characters.RemoveUndefinedTraits(traitMapper);
250251
}
251252
);
252-
253+
253254
Characters.ImportImperatorCharacters(
254255
impWorld,
255256
religionMapper,
@@ -324,14 +325,14 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
324325
impWorld.CoaMapper,
325326
countyLevelGovernorships
326327
);
327-
328+
328329
// Give counties to rulers and governors.
329330
OverwriteCountiesHistory(impWorld.Countries, impWorld.JobsDB.Governorships, countyLevelCountries, countyLevelGovernorships, impWorld.Characters, impWorld.Provinces, CorrectedDate);
330331
ImportImperatorHoldingsIfNotDisabledByConfiguration(impWorld, config);
331332

332333
LandedTitles.ImportDevelopmentFromImperator(Provinces, CorrectedDate, config.ImperatorCivilizationWorth);
333334
LandedTitles.RemoveInvalidLandlessTitles(config.CK3BookmarkDate);
334-
335+
335336
// Apply region-specific tweaks.
336337
HandleIcelandAndFaroeIslands(impWorld, config);
337338

@@ -343,15 +344,15 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
343344
if (!config.StaticDeJure) {
344345
LandedTitles.SetDeJureKingdomsAndEmpires(config.CK3BookmarkDate, Cultures, Characters, MapData, LocDB);
345346
}
346-
347+
347348
Dynasties.SetCoasForRulingDynasties(LandedTitles, config.CK3BookmarkDate);
348-
349+
349350
Characters.RemoveEmployerIdFromLandedCharacters(LandedTitles, CorrectedDate);
350351
Characters.PurgeUnneededCharacters(LandedTitles, Dynasties, DynastyHouses, config.CK3BookmarkDate);
351352
// We could convert Imperator character DNA while importing the characters.
352353
// But that'd be wasteful, because some of them are purged. So, we do it now.
353354
Characters.ConvertImperatorCharacterDNA(dnaFactory);
354-
355+
355356
// If there's a gap between the I:R save date and the CK3 bookmark date,
356357
// generate successors for old I:R characters instead of making them live for centuries.
357358
if (config.CK3BookmarkDate.DiffInYears(impWorld.EndDate) > 1) {
@@ -361,20 +362,20 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac
361362
// Gold needs to be distributed after characters' successors are generated.
362363
Characters.DistributeCountriesGold(LandedTitles, config);
363364
Characters.ImportLegions(LandedTitles, impWorld.Units, impWorld.Characters, CorrectedDate, unitTypeMapper, MenAtArmsTypes, provinceMapper, LocDB, config);
364-
365+
365366
// After the purging of unneeded characters, we should clean up the title history.
366367
LandedTitles.CleanUpHistory(Characters, config.CK3BookmarkDate);
367-
368+
368369
// Now that the title history is basically done, convert officials as council members and courtiers.
369370
LandedTitles.ImportImperatorGovernmentOffices(impWorld.JobsDB.OfficeJobs, Religions, impWorld.EndDate);
370371

371372
Parallel.Invoke(
372373
() => ImportImperatorWars(impWorld, config.CK3BookmarkDate),
373-
374+
374375
() => {
375376
var holySiteEffectMapper = new HolySiteEffectMapper("configurables/holy_site_effect_mappings.txt");
376377
Religions.DetermineHolySites(Provinces, impWorld.Religions, holySiteEffectMapper, config.CK3BookmarkDate);
377-
378+
378379
Religions.GenerateMissingReligiousHeads(LandedTitles, Characters, Provinces, Cultures, config.CK3BookmarkDate);
379380
Logger.IncrementProgress();
380381
},

ImperatorToCK3/CommonUtils/Map/MapData.cs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,33 @@ public override readonly int GetHashCode() {
4848
private readonly HashSet<ulong> mapEdgeProvinces = [];
4949

5050
private string provincesMapFilename = "provinces.png";
51+
private string adjacenciesFilename = "adjacencies.csv";
5152

5253
public MapData(ModFilesystem modFS) {
53-
string adjacenciesFilename = "adjacencies.csv";
54+
ParseDefaultMap(modFS);
5455

56+
Logger.Info("Loading province positions...");
57+
DetermineProvincePositions(modFS);
58+
Logger.IncrementProgress();
59+
60+
Logger.Info("Loading province adjacencies...");
61+
LoadAdjacencies(adjacenciesFilename, modFS);
62+
63+
DetermineMapEdgeProvinces(modFS);
64+
65+
Logger.Info("Determining province neighbors...");
66+
var provincesMapPath = GetProvincesMapPath(modFS);
67+
if (provincesMapPath is not null) {
68+
using Image<Rgb24> provincesMap = Image.Load<Rgb24>(provincesMapPath);
69+
DetermineNeighbors(provincesMap, ProvinceDefinitions);
70+
}
71+
72+
GroupStaticWaterProvinces();
73+
74+
Logger.IncrementProgress();
75+
}
76+
77+
private void ParseDefaultMap(ModFilesystem modFS) {
5578
Logger.Info("Loading default map data...");
5679
const string defaultMapPath = "map_data/default.map";
5780
var defaultMapParser = new Parser();
@@ -92,26 +115,6 @@ public MapData(ModFilesystem modFS) {
92115
defaultMapParser.IgnoreAndLogUnregisteredItems();
93116
defaultMapParser.ParseGameFile(defaultMapPath, modFS);
94117
Logger.IncrementProgress();
95-
96-
Logger.Info("Loading province positions...");
97-
DetermineProvincePositions(modFS);
98-
Logger.IncrementProgress();
99-
100-
Logger.Info("Loading province adjacencies...");
101-
LoadAdjacencies(adjacenciesFilename, modFS);
102-
103-
DetermineMapEdgeProvinces(modFS);
104-
105-
Logger.Info("Determining province neighbors...");
106-
var provincesMapPath = GetProvincesMapPath(modFS);
107-
if (provincesMapPath is not null) {
108-
using Image<Rgb24> provincesMap = Image.Load<Rgb24>(provincesMapPath);
109-
DetermineNeighbors(provincesMap, ProvinceDefinitions);
110-
}
111-
112-
GroupStaticWaterProvinces();
113-
114-
Logger.IncrementProgress();
115118
}
116119

117120
private void GroupStaticWaterProvinces() {
@@ -199,6 +202,7 @@ public IReadOnlySet<ulong> GetNeighborProvinceIds(ulong provinceId) {
199202

200203
private bool IsStaticWater(ulong provinceId) => ProvinceDefinitions[provinceId].IsStaticWater;
201204
private bool IsRiver(ulong provinceId) => ProvinceDefinitions[provinceId].IsRiver;
205+
internal bool IsLand(ulong provinceId) => ProvinceDefinitions[provinceId].IsLand;
202206

203207
public FrozenSet<ulong> ColorableImpassableProvinceIds => ProvinceDefinitions
204208
.Where(p => p.IsColorableImpassable).Select(p => p.Id)

ImperatorToCK3/CommonUtils/Map/ProvinceDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ public void AddSpecialCategory(SpecialProvinceCategory category) {
1616
specialCategories.Contains(SpecialProvinceCategory.ColorableImpassable);
1717
public bool IsStaticWater => specialCategories.Contains(SpecialProvinceCategory.StaticWater);
1818
public bool IsRiver => specialCategories.Contains(SpecialProvinceCategory.River);
19-
public bool IsLand => !IsStaticWater && !IsRiver;
19+
public bool IsLand => (!IsStaticWater && !IsRiver) || IsColorableImpassable; // handles provinces 1107 and 1108 being both impassable_mountains and lakes as of CK3 1.17.1
2020
}

0 commit comments

Comments
 (0)