From 016c26934b94ae9066490a3a4b064f93742fe0f1 Mon Sep 17 00:00:00 2001 From: IhateTrains Date: Thu, 14 Dec 2023 02:47:31 +0000 Subject: [PATCH] Fix exception when using unsupported CK3 map mod combinations (#1636) #patch Sentry event ID: 6a21957012454c3f9a7267777859cfd3 --- .../CK3/Titles/TitleTests.cs | 2 +- ImperatorToCK3/CK3/Provinces/Province.cs | 4 +- .../CK3/Religions/ReligionCollection.cs | 6 +- ImperatorToCK3/CK3/Titles/LandedTitles.cs | 66 +++++++++++-------- ImperatorToCK3/CK3/Titles/Title.cs | 12 ++-- ImperatorToCK3/CK3/World.cs | 28 +++++--- ImperatorToCK3/Mappers/Region/CK3Region.cs | 2 +- .../Mappers/Region/CK3RegionMapper.cs | 4 +- ImperatorToCK3/Mappers/TagTitle/Mapping.cs | 2 +- .../Mappers/TagTitle/TagTitleMapper.cs | 2 +- 10 files changed, 74 insertions(+), 54 deletions(-) diff --git a/ImperatorToCK3.UnitTests/CK3/Titles/TitleTests.cs b/ImperatorToCK3.UnitTests/CK3/Titles/TitleTests.cs index a513f83a4..fbc5590c2 100644 --- a/ImperatorToCK3.UnitTests/CK3/Titles/TitleTests.cs +++ b/ImperatorToCK3.UnitTests/CK3/Titles/TitleTests.cs @@ -203,7 +203,7 @@ public void CapitalBaronyDefaultsToNull() { var titles = new Title.LandedTitles(); var title = titles.Add("k_testtitle"); - Assert.Null(title.CapitalBaronyProvince); + Assert.Null(title.CapitalBaronyProvinceId); } [Fact] diff --git a/ImperatorToCK3/CK3/Provinces/Province.cs b/ImperatorToCK3/CK3/Provinces/Province.cs index 505048b2f..1eb23750e 100644 --- a/ImperatorToCK3/CK3/Provinces/Province.cs +++ b/ImperatorToCK3/CK3/Provinces/Province.cs @@ -294,8 +294,8 @@ private void SetHoldingFromImperator(Title.LandedTitles landedTitles) { public bool IsCountyCapital(Title.LandedTitles landedTitles) { var capitalProvIds = landedTitles - .Where(t => t.CapitalBaronyProvince is not null) - .Select(t => (ulong)t.CapitalBaronyProvince!); + .Where(t => t.CapitalBaronyProvinceId is not null) + .Select(t => (ulong)t.CapitalBaronyProvinceId!); return capitalProvIds.Contains(Id); } } \ No newline at end of file diff --git a/ImperatorToCK3/CK3/Religions/ReligionCollection.cs b/ImperatorToCK3/CK3/Religions/ReligionCollection.cs index 41ad78bed..e458ebaee 100644 --- a/ImperatorToCK3/CK3/Religions/ReligionCollection.cs +++ b/ImperatorToCK3/CK3/Religions/ReligionCollection.cs @@ -137,9 +137,9 @@ public void LoadDoctrines(ModFilesystem ck3ModFS) { return null; } - var capitalBaronyProvince = landedTitles[holySite.CountyId].CapitalBaronyProvince; - if (capitalBaronyProvince is not null) { - return landedTitles.GetBaronyForProvince((ulong)capitalBaronyProvince); + var capitalBaronyProvinceId = landedTitles[holySite.CountyId].CapitalBaronyProvinceId; + if (capitalBaronyProvinceId is not null) { + return landedTitles.GetBaronyForProvince((ulong)capitalBaronyProvinceId); } return null; diff --git a/ImperatorToCK3/CK3/Titles/LandedTitles.cs b/ImperatorToCK3/CK3/Titles/LandedTitles.cs index d1191feb0..d58814c25 100644 --- a/ImperatorToCK3/CK3/Titles/LandedTitles.cs +++ b/ImperatorToCK3/CK3/Titles/LandedTitles.cs @@ -217,7 +217,7 @@ public override void Remove(string name) { } public Title? GetCountyForProvince(ulong provinceId) { foreach (var county in this.Where(title => title.Rank == TitleRank.county)) { - if (county.CountyProvinces.Contains(provinceId)) { + if (county.CountyProvinceIds.Contains(provinceId)) { return county; } } @@ -593,7 +593,7 @@ public void SetDeJureKingdomsAndEmpires(Date ck3BookmarkDate, ProvinceCollection continue; } kingdomRealmShares.TryGetValue(kingdomRealm.Id, out var currentCount); - kingdomRealmShares[kingdomRealm.Id] = currentCount + county.CountyProvinces.Count(); + kingdomRealmShares[kingdomRealm.Id] = currentCount + county.CountyProvinceIds.Count(); } if (kingdomRealmShares.Count > 0) { var biggestShare = kingdomRealmShares.MaxBy(pair => pair.Value); @@ -608,7 +608,7 @@ public void SetDeJureKingdomsAndEmpires(Date ck3BookmarkDate, ProvinceCollection var empireShares = new Dictionary(); var kingdomProvincesCount = 0; foreach (var county in kingdom.GetDeJureVassalsAndBelow("c").Values) { - var countyProvincesCount = county.CountyProvinces.Count(); + var countyProvincesCount = county.CountyProvinceIds.Count(); kingdomProvincesCount += countyProvincesCount; var empireRealm = county.GetRealmOfRank(TitleRank.empire, ck3BookmarkDate); @@ -638,7 +638,7 @@ public void SetDeJureKingdomsAndEmpires(Date ck3BookmarkDate, ProvinceCollection foreach (var kingdom in kingdomsWithoutEmpire) { var counties = kingdom.GetDeJureVassalsAndBelow("c").Values; - var kingdomProvinceIds = counties.SelectMany(c => c.CountyProvinces).ToImmutableHashSet(); + var kingdomProvinceIds = counties.SelectMany(c => c.CountyProvinceIds).ToImmutableHashSet(); var kingdomProvinces = ck3Provinces.Where(p => kingdomProvinceIds.Contains(p.Id)); var dominantHeritage = kingdomProvinces .Select(p => new { Province = p, p.GetCulture(ck3BookmarkDate, ck3Cultures)?.Heritage}) @@ -687,38 +687,20 @@ private HashSet GetCountyHolderIds(Date date) { } public void ImportDevelopmentFromImperator(ProvinceCollection ck3Provinces, Date date, double irCivilizationWorth) { - static (Dictionary, Dictionary) GetIRProvsPerCounty(ProvinceCollection ck3Provinces, IEnumerable counties) { - var impProvsPerCounty = new Dictionary<string, int>(); - var ck3ProvsPerImperatorProv = new Dictionary<ulong, int>(); - foreach (var county in counties) { - var imperatorProvs = new HashSet<ulong>(); - foreach (var ck3ProvId in county.CountyProvinces) { - var ck3Province = ck3Provinces[ck3ProvId]; - var sourceProvinces = ck3Province.ImperatorProvinces; - foreach (var irProvince in sourceProvinces) { - imperatorProvs.Add(irProvince.Id); - ck3ProvsPerImperatorProv.TryGetValue(irProvince.Id, out var currentValue); - ck3ProvsPerImperatorProv[irProvince.Id] = currentValue + 1; - } - } - - impProvsPerCounty[county.Id] = imperatorProvs.Count; - } - - return (impProvsPerCounty, ck3ProvsPerImperatorProv); - } - static bool IsCountyOutsideImperatorMap(Title county, IReadOnlyDictionary<string, int> impProvsPerCounty) { return impProvsPerCounty[county.Id] == 0; } double CalculateCountyDevelopment(Title county, IReadOnlyDictionary<ulong, int> ck3ProvsPerIRProv) { double dev = 0; - var countyProvinces = county.CountyProvinces; - var provsCount = 0; - foreach (var ck3ProvId in countyProvinces) { + IEnumerable<ulong> countyProvinceIds = county.CountyProvinceIds; + int provsCount = 0; + foreach (var ck3ProvId in countyProvinceIds) { + if (!ck3Provinces.TryGetValue(ck3ProvId, out var ck3Province)) { + Logger.Warn($"CK3 province {ck3ProvId} not found!"); + continue; + } ++provsCount; - var ck3Province = ck3Provinces[ck3ProvId]; var sourceProvinces = ck3Province.ImperatorProvinces; if (sourceProvinces.Count == 0) { continue; @@ -751,6 +733,32 @@ double CalculateCountyDevelopment(Title county, IReadOnlyDictionary<ulong, int> } Logger.IncrementProgress(); + return; + + static (Dictionary<string, int>, Dictionary<ulong, int>) GetIRProvsPerCounty(ProvinceCollection ck3Provinces, IEnumerable<Title> counties) { + Dictionary<string, int> impProvsPerCounty = []; + Dictionary<ulong, int> ck3ProvsPerImperatorProv = []; + foreach (var county in counties) { + HashSet<ulong> imperatorProvs = []; + foreach (ulong ck3ProvId in county.CountyProvinceIds) { + if (!ck3Provinces.TryGetValue(ck3ProvId, out var ck3Province)) { + Logger.Warn($"CK3 province {ck3ProvId} not found!"); + continue; + } + + var sourceProvinces = ck3Province.ImperatorProvinces; + foreach (var irProvince in sourceProvinces) { + imperatorProvs.Add(irProvince.Id); + ck3ProvsPerImperatorProv.TryGetValue(irProvince.Id, out var currentValue); + ck3ProvsPerImperatorProv[irProvince.Id] = currentValue + 1; + } + } + + impProvsPerCounty[county.Id] = imperatorProvs.Count; + } + + return (impProvsPerCounty, ck3ProvsPerImperatorProv); + } } public IEnumerable<Title> GetCountriesImportedFromImperator() { diff --git a/ImperatorToCK3/CK3/Titles/Title.cs b/ImperatorToCK3/CK3/Titles/Title.cs index 3cb660c5e..0a31c966d 100644 --- a/ImperatorToCK3/CK3/Titles/Title.cs +++ b/ImperatorToCK3/CK3/Titles/Title.cs @@ -1012,7 +1012,7 @@ private void TrySetCapitalBarony() { ulong baronyProvinceId = (ulong)deJureVassal.Province; if (deJureVassal.Id == CapitalBaronyId) { - CapitalBaronyProvince = baronyProvinceId; + CapitalBaronyProvinceId = baronyProvinceId; break; } } @@ -1063,7 +1063,7 @@ public ISet<ulong> GetProvincesInCountry(Date date) { var heldProvinces = new HashSet<ulong>(); // add directly held counties foreach (var county in heldCounties) { - heldProvinces.UnionWith(county.CountyProvinces); + heldProvinces.UnionWith(county.CountyProvinceIds); } // add vassals' counties foreach (var vassal in GetDeFactoVassalsAndBelow(date).Values) { @@ -1076,7 +1076,7 @@ public ISet<ulong> GetProvincesInCountry(Date date) { parentCollection.Where(t => t.GetHolderId(date) == vassalHolderId && t.Rank == TitleRank.county) ); foreach (var vassalCounty in heldVassalCounties) { - heldProvinces.UnionWith(vassalCounty.CountyProvinces); + heldProvinces.UnionWith(vassalCounty.CountyProvinceIds); } } return heldProvinces; @@ -1099,7 +1099,7 @@ public bool DuchyContainsProvince(ulong provinceId) { return false; } - return DeJureVassals.Any(vassal => vassal.Rank == TitleRank.county && vassal.CountyProvinces.Contains(provinceId)); + return DeJureVassals.Any(vassal => vassal.Rank == TitleRank.county && vassal.CountyProvinceIds.Contains(provinceId)); } public Title GetTopRealm(Date date) { @@ -1153,9 +1153,9 @@ public Title GetTopRealm(Date date) { } // used by county titles only - [commonItems.Serialization.NonSerialized] public IEnumerable<ulong> CountyProvinces => DeJureVassals.Where(v => v.Rank == TitleRank.barony).Select(v => (ulong)v.Province!); + [commonItems.Serialization.NonSerialized] public IEnumerable<ulong> CountyProvinceIds => DeJureVassals.Where(v => v.Rank == TitleRank.barony).Select(v => (ulong)v.Province!); [commonItems.Serialization.NonSerialized] private string CapitalBaronyId { get; set; } = string.Empty; // used when parsing inside county to save first barony - [commonItems.Serialization.NonSerialized] public ulong? CapitalBaronyProvince { get; private set; } // county barony's province; 0 is not a valid barony ID + [commonItems.Serialization.NonSerialized] public ulong? CapitalBaronyProvinceId { get; private set; } // county barony's province; 0 is not a valid barony ID // used by barony titles only [SerializedName("province")] public ulong? Province { get; private set; } // province is area on map. b_barony is its corresponding title. diff --git a/ImperatorToCK3/CK3/World.cs b/ImperatorToCK3/CK3/World.cs index 47e3f06a2..6c26231ab 100644 --- a/ImperatorToCK3/CK3/World.cs +++ b/ImperatorToCK3/CK3/World.cs @@ -360,11 +360,11 @@ private void OverwriteCountiesHistory(IEnumerable<Governorship> governorships, I var countyLevelGovernorshipsSet = countyLevelGovernorships.ToHashSet(); foreach (var county in LandedTitles.Where(t => t.Rank == TitleRank.county)) { - if (county.CapitalBaronyProvince is null) { + if (county.CapitalBaronyProvinceId is null) { Logger.Warn($"County {county} has no capital barony province!"); continue; } - ulong capitalBaronyProvinceId = (ulong)county.CapitalBaronyProvince; + ulong capitalBaronyProvinceId = (ulong)county.CapitalBaronyProvinceId; if (capitalBaronyProvinceId == 0) { // title's capital province has an invalid ID (0 is not a valid province in CK3) Logger.Warn($"County {county} has invalid capital barony province!"); @@ -590,7 +590,7 @@ private void HandleIcelandAndFaroeIslands(Configuration config) { foreach (var county in title.GetDeJureVassalsAndBelow(rankFilter: "c").Values) { county.SetHolder(hermit, bookmarkDate); county.SetDevelopmentLevel(0, bookmarkDate); - foreach (var provinceId in county.CountyProvinces) { + foreach (var provinceId in county.CountyProvinceIds) { var province = Provinces[provinceId]; province.History.RemoveHistoryPastDate("1.1.1"); province.SetFaithId(faithId, date: null); @@ -689,14 +689,26 @@ private void GenerateFillerHoldersForUnownedLands(CultureCollection cultures, Co } var candidateProvinces = new OrderedSet<Province>(); - if (county.CapitalBaronyProvince is not null) { + if (county.CapitalBaronyProvinceId is not null) { // Give priority to capital province. - candidateProvinces.Add(Provinces[county.CapitalBaronyProvince.Value]); + if (Provinces.TryGetValue(county.CapitalBaronyProvinceId.Value, out var capitalProvince)) { + candidateProvinces.Add(capitalProvince); + } } - var allCountyProvinces = county.CountyProvinces - .Select(p => Provinces[p]); + + var allCountyProvinces = county.CountyProvinceIds + .Select(id => Provinces.TryGetValue(id, out var province) ? province : null) + .Where(p => p is not null) + .Select(p => p!); candidateProvinces.UnionWith(allCountyProvinces); - var pseudoRandomSeed = (int)candidateProvinces.First().Id; + + int pseudoRandomSeed; + if (candidateProvinces.Count != 0) { + pseudoRandomSeed = (int)candidateProvinces.First().Id; + } else { + // Use county ID for seed if no province is available. + pseudoRandomSeed = county.Id.Aggregate(0, (current, c) => current + c); + } // Determine culture of the holder. var culture = candidateProvinces diff --git a/ImperatorToCK3/Mappers/Region/CK3Region.cs b/ImperatorToCK3/Mappers/Region/CK3Region.cs index c5b108da5..b49e81b8f 100644 --- a/ImperatorToCK3/Mappers/Region/CK3Region.cs +++ b/ImperatorToCK3/Mappers/Region/CK3Region.cs @@ -61,7 +61,7 @@ public bool ContainsProvince(ulong provinceId) { if (Duchies.Values.Any(duchy => duchy.DuchyContainsProvince(provinceId))) { return true; } - if (Counties.Values.Any(county => county.CountyProvinces.Contains(provinceId))) { + if (Counties.Values.Any(county => county.CountyProvinceIds.Contains(provinceId))) { return true; } return Provinces.Contains(provinceId); diff --git a/ImperatorToCK3/Mappers/Region/CK3RegionMapper.cs b/ImperatorToCK3/Mappers/Region/CK3RegionMapper.cs index 959697b51..22a17245b 100644 --- a/ImperatorToCK3/Mappers/Region/CK3RegionMapper.cs +++ b/ImperatorToCK3/Mappers/Region/CK3RegionMapper.cs @@ -48,7 +48,7 @@ public bool ProvinceIsInRegion(ulong provinceId, string regionName) { } // And sometimes they don't mean what people think they mean at all. - return counties.TryGetValue(regionName, out var county) && county.CountyProvinces.Contains(provinceId); + return counties.TryGetValue(regionName, out var county) && county.CountyProvinceIds.Contains(provinceId); } public bool RegionNameIsValid(string regionName) { if (regions.ContainsKey(regionName)) { @@ -68,7 +68,7 @@ public bool RegionNameIsValid(string regionName) { } public string? GetParentCountyName(ulong provinceId) { foreach (var (countyName, county) in counties) { - if (county.CountyProvinces.Contains(provinceId)) { + if (county.CountyProvinceIds.Contains(provinceId)) { return countyName; } } diff --git a/ImperatorToCK3/Mappers/TagTitle/Mapping.cs b/ImperatorToCK3/Mappers/TagTitle/Mapping.cs index e82115530..a0fe73dcd 100644 --- a/ImperatorToCK3/Mappers/TagTitle/Mapping.cs +++ b/ImperatorToCK3/Mappers/TagTitle/Mapping.cs @@ -38,7 +38,7 @@ public class Mapping { } var ck3ProvincesInDuchy = duchy.GetDeJureVassalsAndBelow("c").Values - .SelectMany(c => c.CountyProvinces) + .SelectMany(c => c.CountyProvinceIds) .ToImmutableHashSet(); var governorshipProvincesInDuchy = governorship.GetCK3ProvinceIds(irProvinces, provMapper) diff --git a/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs b/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs index c7f5e5f9b..d2071e3fc 100644 --- a/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs +++ b/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs @@ -109,7 +109,7 @@ public void RegisterGovernorship(string imperatorRegion, string imperatorCountry private string? GetCountyForGovernorship(Governorship governorship, Country country, Title.LandedTitles titles, ProvinceCollection ck3Provinces, ImperatorRegionMapper imperatorRegionMapper) { foreach (var county in titles.Where(t => t.Rank == TitleRank.county)) { - ulong capitalBaronyProvinceId = (ulong)county.CapitalBaronyProvince!; + ulong capitalBaronyProvinceId = (ulong)county.CapitalBaronyProvinceId!; if (capitalBaronyProvinceId == 0) { // title's capital province has an invalid ID (0 is not a valid province in CK3) continue;