Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a name-based country mapping to e_byzantium #2467

Merged
merged 8 commits into from
Jan 22, 2025
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
50 changes: 44 additions & 6 deletions ImperatorToCK3.UnitTests/Mappers/TagTitle/TitleMappingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion ImperatorToCK3/Data_Files/configurables/title_map.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 }
Expand Down
16 changes: 14 additions & 2 deletions ImperatorToCK3/Imperator/Countries/Country.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ internal sealed partial class Country : IIdentifiable<ulong> {
public string? Religion { get; private set; }
public List<RulerTerm> RulerTerms { get; set; } = [];
public Dictionary<string, int> 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;
Expand All @@ -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; }
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Imperator/Countries/CountryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
27 changes: 12 additions & 15 deletions ImperatorToCK3/Imperator/Countries/CountryName.cs
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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};
}
}
17 changes: 7 additions & 10 deletions ImperatorToCK3/Mappers/TagTitle/TagTitleMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
21 changes: 17 additions & 4 deletions ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
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;

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) {

Check failure on line 20 in ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs#L20

Correct this '&' to '&&' and extract the right operand to a variable if it should always be evaluated.
return null;
}

if (imperatorTagOrRegion is not null & imperatorTagOrRegion != tagForMapping) {

Check failure on line 24 in ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

ImperatorToCK3/Mappers/TagTitle/TitleMapping.cs#L24

Correct this '&' to '&&' and extract the right operand to a variable if it should always be evaluated.
return null;
}
if (maxTitleRank < CK3TitleRank) {
Expand All @@ -22,6 +30,9 @@
if (ranks.Count > 0 && !ranks.Contains(rank)) {
return null;
}
if (irNameKeys.Count > 0 && !irNameKeys.Contains(country.CountryName.Name)) {
return null;
}
return ck3TitleId;
}

Expand Down Expand Up @@ -60,8 +71,9 @@
}

private string ck3TitleId = string.Empty;
private string imperatorTagOrRegion = string.Empty;
private string? imperatorTagOrRegion;
private readonly SortedSet<TitleRank> ranks = [];
private readonly SortedSet<string> irNameKeys = [];

private TitleRank CK3TitleRank => Title.GetRankForId(ck3TitleId);

Expand All @@ -74,6 +86,7 @@
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) {
Expand Down
Loading