From 4e7f038f395f74fe2b7ef7132088f40a22e83934 Mon Sep 17 00:00:00 2001 From: IhateTrains Date: Wed, 22 Jan 2025 21:40:59 +0100 Subject: [PATCH] Add a name-based country mapping to e_byzantium (#2467) closes #2396 Results from 2 conversions: - save from 2022: ![obraz](https://github.com/user-attachments/assets/d00b8db3-958e-41de-9e11-86d8ecd5b693) - save from 2024: ![obraz](https://github.com/user-attachments/assets/d24ec277-e4ae-49a8-b4ab-bc84f4498c8b) --- .github/workflows/build.yml | 1 + .../Mappers/TagTitle/TagTitleMapperTests.cs | 6 +-- .../Mappers/TagTitle/TitleMappingTests.cs | 50 ++++++++++++++++--- .../Data_Files/configurables/title_map.txt | 12 ++++- ImperatorToCK3/Imperator/Countries/Country.cs | 16 +++++- .../Imperator/Countries/CountryFactory.cs | 4 +- .../Imperator/Countries/CountryName.cs | 27 +++++----- .../Mappers/TagTitle/TagTitleMapper.cs | 17 +++---- .../Mappers/TagTitle/TitleMapping.cs | 21 ++++++-- 9 files changed, 111 insertions(+), 43 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e33a8dec3..d13c31ea6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ jobs: strategy: matrix: os: [[self-hosted, windows], [self-hosted, linux], macos-14] + fail-fast: false runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/ImperatorToCK3.UnitTests/Mappers/TagTitle/TagTitleMapperTests.cs b/ImperatorToCK3.UnitTests/Mappers/TagTitle/TagTitleMapperTests.cs index 63cd5f2c9..daa212e59 100644 --- a/ImperatorToCK3.UnitTests/Mappers/TagTitle/TagTitleMapperTests.cs +++ b/ImperatorToCK3.UnitTests/Mappers/TagTitle/TagTitleMapperTests.cs @@ -191,16 +191,16 @@ public void TitleCanBeGeneratedFromGovernorship() { } [Fact] - public void GetTitleForTagReturnsNullOnEmptyTag() { + public void GetTitleForTagUsesCountryIdIfTagIsEmpty() { var mapper = new TagTitleMapper(tagTitleMappingsPath, governorshipTitleMappingsPath, rankMappingsPath); var country = Country.Parse(new BufferedReader(string.Empty), 1); Assert.Empty(country.Tag); var match = mapper.GetTitleForTag(country, "", maxTitleRank: TitleRank.empire); - Assert.Null(match); + Assert.Equal("d_IRTOCK3_id_1", match); } [Fact] - public void GetTitleGovernorshipTagReturnsNullOnCountryWithNoCK3Title() { + public void GetTitleForGovernorshipReturnsNullOnCountryWithNoCK3Title() { var output = new StringWriter(); Console.SetOut(output); diff --git a/ImperatorToCK3.UnitTests/Mappers/TagTitle/TitleMappingTests.cs b/ImperatorToCK3.UnitTests/Mappers/TagTitle/TitleMappingTests.cs index c1ad40cd5..da4dead77 100644 --- a/ImperatorToCK3.UnitTests/Mappers/TagTitle/TitleMappingTests.cs +++ b/ImperatorToCK3.UnitTests/Mappers/TagTitle/TitleMappingTests.cs @@ -25,7 +25,9 @@ public class TitleMappingTests { public void SimpleTagMatch() { var reader = new BufferedReader("{ ck3 = e_roman_empire ir = ROM }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Equal("e_roman_empire", match); } @@ -34,7 +36,9 @@ public void SimpleTagMatch() { public void SimpleTagMatchFailsOnWrongTag() { var reader = new BufferedReader("{ ck3 = e_roman_empire ir = REM }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Null(match); } @@ -43,7 +47,9 @@ public void SimpleTagMatchFailsOnWrongTag() { public void SimpleTagMatchFailsOnNoTag() { var reader = new BufferedReader("{ ck3 = e_roman_empire }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Null(match); } @@ -52,7 +58,9 @@ public void SimpleTagMatchFailsOnNoTag() { public void TagRankMatch() { var reader = new BufferedReader("{ ck3 = e_roman_empire ir = ROM rank = e }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Equal("e_roman_empire", match); } @@ -61,7 +69,9 @@ public void TagRankMatch() { public void TagRankMatchFailsOnWrongRank() { var reader = new BufferedReader("{ ck3 = e_roman_empire ir = ROM rank = k }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Null(match); } @@ -70,11 +80,39 @@ public void TagRankMatchFailsOnWrongRank() { public void TagRankMatchSucceedsOnNoRank() { var reader = new BufferedReader("{ ck3 = e_roman_empire ir = ROM }"); var mapping = TitleMapping.Parse(reader); - var match = mapping.RankMatch("ROM", TitleRank.empire, maxTitleRank: TitleRank.empire); + + var country = new Country(1) {Tag = "ROM"}; + var match = mapping.RankMatch(country, TitleRank.empire, maxTitleRank: TitleRank.empire); Assert.Equal("e_roman_empire", match); } + [Fact] + public void CountryNameCanBeUsedInAMapping() { + var reader = new BufferedReader(""" + { + ck3 = e_byzantium + ir_name_key = ERE + ir_name_key = eastern_roman_republic_name # multiple ir_name_keys are allowed + } + """); + var mapping = TitleMapping.Parse(reader); + + // If the mapping has no "ir" parameter, and the country name key matches the ones in the mapping, + // the mapping should match regardless of the tag. + var ereCountry1 = new Country(1) {Tag = "X01", CountryName = new() { Name = "ERE" }}; + var ereCountry2 = new Country(2) {Tag = "X02", CountryName = new() { Name = "eastern_roman_republic_name" }}; + Assert.Equal("e_byzantium", mapping.RankMatch(ereCountry1, TitleRank.empire, maxTitleRank: TitleRank.empire)); + Assert.Equal("e_byzantium", mapping.RankMatch(ereCountry2, TitleRank.empire, maxTitleRank: TitleRank.empire)); + + var nonEreCountry = new Country(3) {Tag = "X03", CountryName = new() { Name = "non_ERE" }}; + Assert.Null(mapping.RankMatch(nonEreCountry, TitleRank.empire, maxTitleRank: TitleRank.empire)); + + // A country with tag ERE should not match, we're comparing names! + var ereTagCountry = new Country(4) {Tag = "ERE", CountryName = new() { Name = "non_ERE" }}; + Assert.Null(mapping.RankMatch(ereTagCountry, TitleRank.empire, maxTitleRank: TitleRank.empire)); + } + [Fact] public void GovernorshipToDeJureDuchyMappingFailsIfDuchyIsNot60PercentControlled() { var irProvinces = new ProvinceCollection { diff --git a/ImperatorToCK3/Data_Files/configurables/title_map.txt b/ImperatorToCK3/Data_Files/configurables/title_map.txt index 7f4f7bd4b..4406922b8 100644 --- a/ImperatorToCK3/Data_Files/configurables/title_map.txt +++ b/ImperatorToCK3/Data_Files/configurables/title_map.txt @@ -1,8 +1,9 @@ ### I:R - CK3 tag mappings ### -#link = { ir = a ck3 = b rank = c } +#link = { ir = a ck3 = b rank = c ir_name_key = d } #a = the I:R tag #b = the CK3 landed title #c = the rank this country has (can only be "d", "k", "e") +#d = (optional) the localization key the I:R country uses, for example "eastern_roman_republic_name". Multiple entries are allowed. # Only countries with a "Kingdom" or "Empire" in name should be # let map into a title with a different rank than the output @@ -41,6 +42,15 @@ link = { ir = JUD ck3 = k_israel rank = k } # Italic +link = { + ck3 = e_byzantium + ir_name_key = ERE + ir_name_key = eastern_rome_name + ir_name_key = eastern_roman_republic_name + ir_name_key = eastern_roman_empire_name + ir_name_key = eastern_roman_kingdom_name + ir_name_key = eastern_roman_dictatorship_name +} link = { ir = ROM ck3 = e_roman_empire rank = e } link = { ir = ROM ck3 = e_roman_empire rank = k } # Because yes link = { ir = NEP ck3 = k_naples rank = k } diff --git a/ImperatorToCK3/Imperator/Countries/Country.cs b/ImperatorToCK3/Imperator/Countries/Country.cs index c3582ddb9..622f45f45 100644 --- a/ImperatorToCK3/Imperator/Countries/Country.cs +++ b/ImperatorToCK3/Imperator/Countries/Country.cs @@ -18,7 +18,13 @@ internal sealed partial class Country : IIdentifiable { public string? Religion { get; private set; } public List RulerTerms { get; set; } = []; public Dictionary HistoricalRegnalNumbers { get; private set; } = []; - public string Tag { get; private set; } = ""; + + private string tag = ""; + public string Tag { + get => tag; + init => tag = value; + } + private string? historicalTag; public string HistoricalTag { get => historicalTag ?? Tag; @@ -29,7 +35,13 @@ public string HistoricalTag { public Country? OriginCountry { get; private set; } = null; public string Name => CountryName.Name; - public CountryName CountryName { get; private set; } = new(); + + private CountryName countryName = new(); + public CountryName CountryName { + get => countryName; + init => countryName = value; + } + public string Flag { get; private set; } = ""; public CountryType CountryType { get; private set; } = CountryType.real; public ulong? CapitalProvinceId { get; private set; } diff --git a/ImperatorToCK3/Imperator/Countries/CountryFactory.cs b/ImperatorToCK3/Imperator/Countries/CountryFactory.cs index a39105d47..e55d5fc80 100644 --- a/ImperatorToCK3/Imperator/Countries/CountryFactory.cs +++ b/ImperatorToCK3/Imperator/Countries/CountryFactory.cs @@ -24,10 +24,10 @@ internal sealed partial class Country { private static void RegisterCountryKeywords(Parser parser, Country parsedCountry) { var colorFactory = new ColorFactory(); - parser.RegisterKeyword("tag", reader => parsedCountry.Tag = reader.GetString()); + parser.RegisterKeyword("tag", reader => parsedCountry.tag = reader.GetString()); parser.RegisterKeyword("historical", reader => parsedCountry.HistoricalTag = reader.GetString()); parser.RegisterKeyword("origin", reader => parsedCountry.parsedOriginCountryId = reader.GetULong()); - parser.RegisterKeyword("country_name", reader => parsedCountry.CountryName = CountryName.Parse(reader)); + parser.RegisterKeyword("country_name", reader => parsedCountry.countryName = CountryName.Parse(reader)); parser.RegisterKeyword("flag", reader => parsedCountry.Flag = reader.GetString()); parser.RegisterKeyword("country_type", reader => SetCountryType(reader, parsedCountry)); diff --git a/ImperatorToCK3/Imperator/Countries/CountryName.cs b/ImperatorToCK3/Imperator/Countries/CountryName.cs index 3b4651ac5..bed95dff8 100644 --- a/ImperatorToCK3/Imperator/Countries/CountryName.cs +++ b/ImperatorToCK3/Imperator/Countries/CountryName.cs @@ -1,18 +1,14 @@ using commonItems; using commonItems.Localization; -using System; using System.Linq; namespace ImperatorToCK3.Imperator.Countries; -internal sealed class CountryName : ICloneable { - public string Name { get; private set; } = ""; +internal sealed class CountryName { + public string Name { get; init; } = ""; private string? adjective; - public CountryName? BaseName { get; private set; } + public CountryName? BaseName { get; private init; } - public object Clone() { - return new CountryName {Name = Name, adjective = adjective, BaseName = BaseName}; - } public LocBlock? GetNameLocBlock(LocDB irLocDB, CountryCollection imperatorCountries) { // If the name contains a space, it can be a composite name like "egyptian PROV4791_persia" @@ -190,18 +186,19 @@ private string ReplaceDataTypes(string loc, string language, LocDB irLocDB, Coun } public static CountryName Parse(BufferedReader reader) { - var countryName = new CountryName(); - + string parsedName = string.Empty; + string? parsedAdjective = null; + CountryName? parsedBaseName = null; + var parser = new Parser(); - parser.RegisterKeyword("name", r => countryName.Name = r.GetString()); - parser.RegisterKeyword("adjective", r => countryName.adjective = r.GetString()); + parser.RegisterKeyword("name", r => parsedName = r.GetString()); + parser.RegisterKeyword("adjective", r => parsedAdjective = r.GetString()); parser.RegisterKeyword("base", r => { - var tempCountryName = (CountryName)countryName.Clone(); - tempCountryName.BaseName = Parse(r); - countryName = (CountryName)tempCountryName.Clone(); + parsedBaseName = Parse(r); }); parser.IgnoreAndLogUnregisteredItems(); parser.ParseStream(reader); - return countryName; + + return new() {Name = parsedName, adjective = parsedAdjective, BaseName = parsedBaseName}; } } \ No newline at end of file diff --git a/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs b/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs index dd9dccfdc..6c5afd9ae 100644 --- a/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs +++ b/ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs @@ -36,14 +36,6 @@ public void RegisterGovernorship(string imperatorRegion, string imperatorCountry usedTitles.Add(ck3Title); } public string? GetTitleForTag(Country country, string localizedTitleName, TitleRank maxTitleRank) { - // If country has an origin (e.g. rebelled from another country), the historical tag probably points to the original country. - string tagForMapping = country.OriginCountry is not null ? country.Tag : country.HistoricalTag; - - // The only case where we fail is on invalid invocation. Otherwise, failure is not an option! - if (string.IsNullOrEmpty(tagForMapping)) { - return null; - } - // Look up register. if (registeredCountryTitles.TryGetValue(country.Id, out var titleToReturn)) { return titleToReturn; @@ -52,7 +44,7 @@ public void RegisterGovernorship(string imperatorRegion, string imperatorCountry // Attempt a title match. var rank = EnumHelper.Min(GetCK3TitleRank(country, localizedTitleName), maxTitleRank); foreach (var mapping in titleMappings) { - var match = mapping.RankMatch(tagForMapping, rank, maxTitleRank); + var match = mapping.RankMatch(country, rank, maxTitleRank); if (match is not null) { if (usedTitles.Contains(match)) { continue; @@ -244,7 +236,12 @@ private string GenerateNewTitleId(Country country, string localizedTitleName, Ti var ck3TitleId = GetTitlePrefixForRank(ck3Rank); ck3TitleId += GeneratedCK3TitlePrefix; - ck3TitleId += country.Tag; + + if (string.IsNullOrEmpty(country.Tag)) { + ck3TitleId += $"id_{country.Id}"; + } else { + ck3TitleId += country.Tag; + } return ck3TitleId; } diff --git a/ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs b/ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs index 87e72ea73..822b2ba45 100644 --- a/ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs +++ b/ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs @@ -1,10 +1,10 @@ using commonItems; using ImperatorToCK3.CK3.Titles; +using ImperatorToCK3.Imperator.Countries; using ImperatorToCK3.Imperator.Jobs; using ImperatorToCK3.Imperator.Provinces; using ImperatorToCK3.Mappers.Province; using Open.Collections; -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -12,8 +12,16 @@ namespace ImperatorToCK3.Mappers.TagTitle; internal sealed class TitleMapping { - public string? RankMatch(string irTagOrRegion, TitleRank rank, TitleRank maxTitleRank) { - if (imperatorTagOrRegion != irTagOrRegion) { + public string? RankMatch(Country country, TitleRank rank, TitleRank maxTitleRank) { + // If country has an origin (e.g. rebelled from another country), the historical tag probably points to the original country. + string tagForMapping = country.OriginCountry is not null ? country.Tag : country.HistoricalTag; + + // The mapping should have at least an I:R tag or I:R name keys. + if (imperatorTagOrRegion is null & irNameKeys.Count == 0) { + return null; + } + + if (imperatorTagOrRegion is not null & imperatorTagOrRegion != tagForMapping) { return null; } if (maxTitleRank < CK3TitleRank) { @@ -22,6 +30,9 @@ internal sealed class TitleMapping { if (ranks.Count > 0 && !ranks.Contains(rank)) { return null; } + if (irNameKeys.Count > 0 && !irNameKeys.Contains(country.CountryName.Name)) { + return null; + } return ck3TitleId; } @@ -60,8 +71,9 @@ internal sealed class TitleMapping { } private string ck3TitleId = string.Empty; - private string imperatorTagOrRegion = string.Empty; + private string? imperatorTagOrRegion; private readonly SortedSet ranks = []; + private readonly SortedSet irNameKeys = []; private TitleRank CK3TitleRank => Title.GetRankForId(ck3TitleId); @@ -74,6 +86,7 @@ static TitleMapping() { var ranksToAdd = reader.GetString().ToCharArray().Select(TitleRankUtils.CharToTitleRank); mappingToReturn.ranks.AddRange(ranksToAdd); }); + parser.RegisterKeyword("ir_name_key", reader => mappingToReturn.irNameKeys.Add(reader.GetString())); parser.IgnoreAndLogUnregisteredItems(); } public static TitleMapping Parse(BufferedReader reader) {