Skip to content

Remove all many-to-many province mappings #2609

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

Merged
merged 14 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ImperatorToCK3/CK3/Characters/CharacterCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,41 +183,41 @@
}
}

private void LinkMothersAndFathers() {
var motherCounter = 0;
var fatherCounter = 0;
foreach (var ck3Character in this) {
// make links between Imperator characters
if (ck3Character.ImperatorCharacter is null) {
// imperatorRegnal characters do not have ImperatorCharacter
continue;
}
var irMotherCharacter = ck3Character.ImperatorCharacter.Mother;
if (irMotherCharacter is not null) {
var ck3MotherCharacter = irMotherCharacter.CK3Character;
if (ck3MotherCharacter is not null) {
ck3Character.Mother = ck3MotherCharacter;
++motherCounter;
} else {
Logger.Warn($"Imperator mother {irMotherCharacter.Id} has no CK3 character!");
}
}

// make links between Imperator characters
var irFatherCharacter = ck3Character.ImperatorCharacter.Father;
if (irFatherCharacter is not null) {
var ck3FatherCharacter = irFatherCharacter.CK3Character;
if (ck3FatherCharacter is not null) {
ck3Character.Father = ck3FatherCharacter;
++fatherCounter;
} else {
Logger.Warn($"Imperator father {irFatherCharacter.Id} has no CK3 character!");
}
}
}
Logger.Info($"{motherCounter} mothers and {fatherCounter} fathers linked in CK3.");
}

Check notice on line 220 in ImperatorToCK3/CK3/Characters/CharacterCollection.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/CharacterCollection.cs#L186-L220

Complex Method
private void LinkSpouses(Date conversionDate) {
var spouseCounter = 0;
foreach (var ck3Character in this) {
Expand Down Expand Up @@ -382,60 +382,60 @@
Logger.IncrementProgress();
}

private void SetCharacterCastes(CultureCollection cultures, Date ck3BookmarkDate) {
var casteSystemCultureIds = cultures
.Where(c => c.TraditionIds.Contains("tradition_caste_system"))
.Select(c => c.Id)
.ToHashSet();
var learningEducationTraits = new[]{"education_learning_1", "education_learning_2", "education_learning_3", "education_learning_4"};

foreach (var character in this.OrderBy(c => c.BirthDate)) {
if (character.ImperatorCharacter is null) {
continue;
}

var cultureId = character.GetCultureId(ck3BookmarkDate);
if (cultureId is null || !casteSystemCultureIds.Contains(cultureId)) {
continue;
}

// The caste is hereditary.
var father = character.Father;
if (father is not null) {
var foundTrait = GetCasteTraitFromParent(father);
if (foundTrait is not null) {
character.AddBaseTrait(foundTrait);
continue;
}
}
var mother = character.Mother;
if (mother is not null) {
var foundTrait = GetCasteTraitFromParent(mother);
if (foundTrait is not null) {
character.AddBaseTrait(foundTrait);
continue;
}
}

// Try to set caste based on character's traits.
var traitIds = character.BaseTraits.ToHashSet();
character.AddBaseTrait(traitIds.Intersect(learningEducationTraits).Any() ? "brahmin" : "kshatriya");
}
return;

static string? GetCasteTraitFromParent(Character parentCharacter) {
var casteTraits = new[]{"brahmin", "kshatriya", "vaishya", "shudra"};
var parentTraitIds = parentCharacter.BaseTraits.ToHashSet();
return casteTraits.Intersect(parentTraitIds).FirstOrDefault();
}
}

Check notice on line 432 in ImperatorToCK3/CK3/Characters/CharacterCollection.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/CharacterCollection.cs#L385-L432

Complex Method
public void LoadCharacterIDsToPreserve(Date ck3BookmarkDate) {
Logger.Debug("Loading IDs of CK3 characters to preserve...");

const string configurablePath = "configurables/ck3_characters_to_preserve.txt";
var parser = new Parser();
parser.RegisterRegex("keep_as_is", reader => {
parser.RegisterKeyword("keep_as_is", reader => {
var ids = reader.GetStrings();
foreach (var id in ids) {
if (!TryGetValue(id, out var character)) {
Expand Down Expand Up @@ -468,93 +468,93 @@
parser.ParseFile(configurablePath);
}

public void PurgeUnneededCharacters(Title.LandedTitles titles, DynastyCollection dynasties, HouseCollection houses, Date ck3BookmarkDate) {
Logger.Info("Purging unneeded characters...");

// Characters from CK3 that hold titles at the bookmark date should be kept.
var currentTitleHolderIds = titles.GetHolderIdsForAllTitlesExceptNobleFamilyTitles(ck3BookmarkDate);
var landedCharacters = this
.Where(character => currentTitleHolderIds.Contains(character.Id))
.ToArray();
var charactersToCheck = this.Except(landedCharacters);

// Characters from I:R that held or hold titles should be kept.
var allTitleHolderIds = titles.GetAllHolderIds();
var imperatorTitleHolders = this
.Where(character => character.FromImperator && allTitleHolderIds.Contains(character.Id))
.ToArray();
charactersToCheck = charactersToCheck.Except(imperatorTitleHolders);

// Don't purge animation_test characters.
charactersToCheck = charactersToCheck
.Where(c => !c.Id.StartsWith("animation_test_"));

// Keep alive Imperator characters.
charactersToCheck = charactersToCheck
.Where(c => c is not {FromImperator: true, ImperatorCharacter.IsDead: false});

// Make some exceptions for characters referenced in game's script files.
charactersToCheck = charactersToCheck
.Where(character => !character.IsNonRemovable)
.ToArray();

// I:R members of landed dynasties will be preserved, unless dead and childless.
var dynastyIdsOfLandedCharacters = landedCharacters
.Select(character => character.GetDynastyId(ck3BookmarkDate))
.Distinct()
.Where(id => id is not null)
.ToHashSet();

var i = 0;
var charactersToRemove = new List<Character>();
var parentIdsCache = new HashSet<string>();
do {
Logger.Debug($"Beginning iteration {i} of characters purge...");
charactersToRemove.Clear();
parentIdsCache.Clear();
++i;

// Build cache of all parent IDs.
foreach (var character in this) {
var motherId = character.MotherId;
if (motherId is not null) {
parentIdsCache.Add(motherId);
}

var fatherId = character.FatherId;
if (fatherId is not null) {
parentIdsCache.Add(fatherId);
}
}

// See who can be removed.
foreach (var character in charactersToCheck) {
// Is the character from Imperator and do they belong to a dynasty that holds or held titles?
if (character.FromImperator && dynastyIdsOfLandedCharacters.Contains(character.GetDynastyId(ck3BookmarkDate))) {
// Is the character dead and childless? Purge.
if (!parentIdsCache.Contains(character.Id)) {
charactersToRemove.Add(character);
}

continue;
}

charactersToRemove.Add(character);
}

BulkRemove(charactersToRemove.ConvertAll(c => c.Id));

Logger.Debug($"\tPurged {charactersToRemove.Count} unneeded characters in iteration {i}.");
charactersToCheck = charactersToCheck.Except(charactersToRemove).ToArray();
} while(charactersToRemove.Count > 0);

// At this point we probably have many dynasties with no characters left.
// Let's purge them.
houses.PurgeUnneededHouses(this, ck3BookmarkDate);
dynasties.PurgeUnneededDynasties(this, houses, ck3BookmarkDate);
dynasties.FlattenDynastiesWithNoFounders(this, houses, ck3BookmarkDate);
}

Check notice on line 557 in ImperatorToCK3/CK3/Characters/CharacterCollection.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/CharacterCollection.cs#L471-L557

Complex Method
public void RemoveEmployerIdFromLandedCharacters(Title.LandedTitles titles, Date conversionDate) {
Logger.Info("Removing employer id from landed characters...");
var landedCharacterIds = titles.GetHolderIdsForAllTitlesExceptNobleFamilyTitles(conversionDate);
Expand Down Expand Up @@ -661,154 +661,154 @@
Logger.IncrementProgress();
}

public void GenerateSuccessorsForOldCharacters(Title.LandedTitles titles, CultureCollection cultures, Date irSaveDate, Date ck3BookmarkDate, ulong randomSeed) {
Logger.Info("Generating successors for old characters...");

var oldCharacters = this
.Where(c => c.BirthDate < ck3BookmarkDate && c.DeathDate is null && ck3BookmarkDate.DiffInYears(c.BirthDate) > 60).ToArray();

var titleHolderIds = titles.GetHolderIdsForAllTitlesExceptNobleFamilyTitles(ck3BookmarkDate);

var oldTitleHolders = oldCharacters
.Where(c => titleHolderIds.Contains(c.Id))
.ToArray();

// For characters that don't hold any titles, just set up a death date.
var randomForCharactersWithoutTitles = new Random((int)randomSeed);
foreach (var oldCharacter in oldCharacters.Except(oldTitleHolders)) {
// Roll a dice to determine how much longer the character will live.
var yearsToLive = randomForCharactersWithoutTitles.Next(0, 30);

// If the character is female and pregnant, make sure she doesn't die before the pregnancy ends.
if (oldCharacter is {Female: true, ImperatorCharacter: not null}) {
var lastPregnancy = oldCharacter.Pregnancies.OrderBy(p => p.BirthDate).LastOrDefault();
if (lastPregnancy is not null) {
oldCharacter.DeathDate = lastPregnancy.BirthDate.ChangeByYears(yearsToLive);
continue;
}
}

oldCharacter.DeathDate = irSaveDate.ChangeByYears(yearsToLive);
}

ConcurrentDictionary<string, Title[]> titlesByHolderId = new(titles
.Select(t => new {Title = t, HolderId = t.GetHolderId(ck3BookmarkDate)})
.Where(t => t.HolderId != "0")
.GroupBy(t => t.HolderId)
.ToDictionary(g => g.Key, g => g.Select(t => t.Title).ToArray()));

ConcurrentDictionary<string, string[]> cultureIdToMaleNames = new(cultures
.ToDictionary(c => c.Id, c => c.MaleNames.ToArray()));

// For title holders, generate successors and add them to title history.
Parallel.ForEach(oldTitleHolders, oldCharacter => {
// Get all titles held by the character.
var heldTitles = titlesByHolderId[oldCharacter.Id];
string? dynastyId = oldCharacter.GetDynastyId(ck3BookmarkDate);
string? dynastyHouseId = oldCharacter.GetDynastyHouseId(ck3BookmarkDate);
string? faithId = oldCharacter.GetFaithId(ck3BookmarkDate);
string? cultureId = oldCharacter.GetCultureId(ck3BookmarkDate);
string[] maleNames;
if (cultureId is not null) {
maleNames = cultureIdToMaleNames[cultureId];
} else {
Logger.Warn($"Failed to find male names for successors of {oldCharacter.Id}.");
maleNames = ["Alexander"];
}

var randomSeedForCharacter = randomSeed ^ (oldCharacter.ImperatorCharacter?.Id ?? 0);
var random = new Random((int)randomSeedForCharacter);

int successorCount = 0;
Character currentCharacter = oldCharacter;
Date currentCharacterBirthDate = currentCharacter.BirthDate;
while (ck3BookmarkDate.DiffInYears(currentCharacterBirthDate) >= 90) {
// If the character has living male children, the oldest one will be the successor.
var successorAndBirthDate = currentCharacter.Children
.Where(c => c is {Female: false, DeathDate: null})
.Select(c => new { Character = c, c.BirthDate })
.OrderBy(x => x.BirthDate)
.FirstOrDefault();

Character successor;
Date currentCharacterDeathDate;
Date successorBirthDate;
if (successorAndBirthDate is not null) {
successor = successorAndBirthDate.Character;
successorBirthDate = successorAndBirthDate.BirthDate;

// Roll a dice to determine how much longer the character will live.
// But make sure the successor is at least 16 years old when the old character dies.
var successorAgeAtBookmarkDate = ck3BookmarkDate.DiffInYears(successorBirthDate);
var yearsUntilSuccessorBecomesAnAdult = Math.Max(16 - successorAgeAtBookmarkDate, 0);

var yearsToLive = random.Next((int)Math.Ceiling(yearsUntilSuccessorBecomesAnAdult), 25);
int currentCharacterAge = random.Next(30 + yearsToLive, 80);
currentCharacterDeathDate = currentCharacterBirthDate.ChangeByYears(currentCharacterAge);
// Needs to be after the save date.
if (currentCharacterDeathDate <= irSaveDate) {
currentCharacterDeathDate = irSaveDate.ChangeByDays(1);
}
} else {
// We don't want all the generated successors on the map to have the same birth date.
var yearsUntilHeir = random.Next(1, 5);

// Make the old character live until the heir is at least 16 years old.
var successorAge = random.Next(yearsUntilHeir + 16, 30);
int currentCharacterAge = random.Next(30 + successorAge, 80);
currentCharacterDeathDate = currentCharacterBirthDate.ChangeByYears(currentCharacterAge);
if (currentCharacterDeathDate <= irSaveDate) {
currentCharacterDeathDate = irSaveDate.ChangeByDays(1);
}

// Generate a new successor.
string id = $"irtock3_{oldCharacter.Id}_successor_{successorCount}";
string firstName = maleNames[random.Next(0, maleNames.Length)];

successorBirthDate = currentCharacterDeathDate.ChangeByYears(-successorAge);
successor = new Character(id, firstName, successorBirthDate, this) {FromImperator = true};
Add(successor);
if (currentCharacter.Female) {
successor.Mother = currentCharacter;
} else {
successor.Father = currentCharacter;
}
if (cultureId is not null) {
successor.SetCultureId(cultureId, null);
}
if (faithId is not null) {
successor.SetFaithId(faithId, null);
}
if (dynastyId is not null) {
successor.SetDynastyId(dynastyId, null);
}
if (dynastyHouseId is not null) {
successor.SetDynastyHouseId(dynastyHouseId, null);
}
}

currentCharacter.DeathDate = currentCharacterDeathDate;
// On the old character death date, the successor should inherit all titles.
foreach (var heldTitle in heldTitles) {
heldTitle.SetHolder(successor, currentCharacterDeathDate);
}

// Move to the successor and repeat the process.
currentCharacter = successor;
currentCharacterBirthDate = successorBirthDate;
++successorCount;
}

// After the loop, currentCharacter should represent the successor at bookmark date.
// Set his DNA to avoid weird looking character on the bookmark screen in CK3.
currentCharacter.DNA = oldCharacter.DNA;

// Transfer gold to the living successor.
currentCharacter.Gold = oldCharacter.Gold;
oldCharacter.Gold = null;
});
}

Check notice on line 811 in ImperatorToCK3/CK3/Characters/CharacterCollection.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/CharacterCollection.cs#L664-L811

Complex Method
internal void ConvertImperatorCharacterDNA(DNAFactory dnaFactory) {
Logger.Info("Converting Imperator character DNA to CK3...");
foreach (var character in this) {
Expand Down

Large diffs are not rendered by default.

9,768 changes: 5,338 additions & 4,430 deletions ImperatorToCK3/Data_Files/configurables/province_mappings/imperator_vanilla.txt

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions ImperatorToCK3/Imperator/Diplomacy/War.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using commonItems;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace ImperatorToCK3.Imperator.Diplomacy;

internal sealed class War {
internal sealed partial class War {
public Date StartDate { get; private set; } = new(1, 1, 1);
public bool Previous { get; private set; }
public List<ulong> AttackerCountryIds { get; } = [];
Expand All @@ -23,7 +24,7 @@ static War() {
parser.RegisterKeyword("defender", reader => {
warToReturn.DefenderCountryIds.Add(reader.GetULong());
});
parser.RegisterRegex(WargoalTypeRegex, reader => {
parser.RegisterRegex(wargoalTypeRegex, reader => {
var wargoalParser = new Parser();
wargoalParser.RegisterKeyword("type", typeReader =>
warToReturn.WarGoal = typeReader.GetString()
Expand All @@ -43,10 +44,14 @@ public static War Parse(BufferedReader reader) {
return warToReturn;
}

// Wargoal types seem to be hardcoded, they don't need to be loaded from game files.
private const string WargoalTypeRegex = "take_province|naval_superiority|superiority|enforce_military_access|independence";
private static readonly Regex wargoalTypeRegex = WargoalTypeRegex();

private static readonly Parser parser = new();
private static War warToReturn = new();
public static IgnoredKeywordsSet IgnoredTokens { get; } = new();
public static IgnoredKeywordsSet IgnoredTokens { get; } = [];

// Wargoal types seem to be hardcoded, they don't need to be loaded from game files.
private const string WargoalTypeRegexStr = "take_province|naval_superiority|superiority|enforce_military_access|independence";
[GeneratedRegex(WargoalTypeRegexStr, RegexOptions.Compiled)]
private static partial Regex WargoalTypeRegex();
}
4 changes: 3 additions & 1 deletion ImperatorToCK3/Imperator/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,70 +159,70 @@
File.WriteAllText(Path.Join(config.ImperatorDocPath, "dlc_load.json"), dlcLoadBuilder.ToString());
}

private void LaunchImperatorToExportCountryFlags(Configuration config) {
Logger.Info("Retrieving random CoAs from Imperator...");
OutputContinueGameJson(config);
OutputDlcLoadJson(config);

string imperatorBinaryName = OperatingSystem.IsWindows() ? "imperator.exe" : "imperator";
var imperatorBinaryPath = Path.Combine(config.ImperatorPath, "binaries", imperatorBinaryName);
if (!File.Exists(imperatorBinaryPath)) {
Logger.Warn("Imperator binary not found! Aborting the random CoA extraction!");
return;
}

string dataTypesLogPath = Path.Combine(config.ImperatorDocPath, "logs/data_types.log");
if (File.Exists(dataTypesLogPath)) {
FileHelper.DeleteWithRetries(dataTypesLogPath);
}

Logger.Debug("Launching Imperator to extract coats of arms...");

var processStartInfo = new ProcessStartInfo {
FileName = imperatorBinaryPath,
Arguments = "-continuelastsave -debug_mode",
CreateNoWindow = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
var imperatorProcess = Process.Start(processStartInfo);
if (imperatorProcess is null) {
Logger.Warn("Failed to start Imperator process! Aborting!");
return;
}

imperatorProcess.Exited += HandleImperatorProcessExit(config, imperatorProcess);

// Make sure that if converter is closed, Imperator is closed as well.
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
if (!imperatorProcess.HasExited) {
imperatorProcess.Kill();
}
};

// Wait until data_types.log exists (it will be created by the dumpdatatypes command).
var stopwatch = new Stopwatch();
stopwatch.Start();
while (!imperatorProcess.HasExited && !File.Exists(dataTypesLogPath)) {
if (stopwatch.Elapsed > TimeSpan.FromMinutes(5)) {
Logger.Warn("Imperator process took too long to execute console commands! Aborting!");
imperatorProcess.Kill();
break;
}

if (imperatorProcess.StandardOutput.ReadLine()?.Contains("Updating cached data done") == true) {
Logger.Debug("Imperator finished loading. Waiting for console commands to execute...");
}

Thread.Sleep(100);
}

if (!imperatorProcess.HasExited) {
Logger.Debug("Killing Imperator process...");
imperatorProcess.Kill();
}
}

Check notice on line 225 in ImperatorToCK3/Imperator/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/Imperator/World.cs#L162-L225

Complex Method
private static EventHandler HandleImperatorProcessExit(Configuration config, Process imperatorProcess) {
return (_, _) => {
Logger.Debug($"Imperator process exited with code {imperatorProcess.ExitCode}. Removing temporary mod files...");
Expand Down Expand Up @@ -336,7 +336,7 @@

Thread? localCoaExtractThread = null;

parser.RegisterRegex(@"\bSAV\w*\b", _ => { });
parser.RegisterRegex(SaveStartRegex(), _ => { });
parser.RegisterKeyword("version", reader => VerifySaveVersion(converterVersion, reader));
parser.RegisterKeyword("date", reader => LoadSaveDate(config, reader));
parser.RegisterKeyword("enabled_dlcs", LogEnabledDLCs);
Expand Down Expand Up @@ -557,81 +557,81 @@
Logger.Debug("Parsing genes...");
genesDB = new GenesDB(ModFS);
}
private void LoadPreImperatorRulers() {
const string filePath = "configurables/characters_prehistory.txt";
const string noRulerWarning = "Pre-Imperator ruler term has no pre-Imperator ruler!";
const string noCountryIdWarning = "Pre-Imperator ruler term has no country ID!";

var preImperatorRulerTerms = new Dictionary<ulong, List<RulerTerm>>(); // <country id, list of terms>
var parser = new Parser();
parser.RegisterKeyword("ruler", reader => {
var rulerTerm = new RulerTerm(reader, Countries);
if (rulerTerm.PreImperatorRuler is null) {
Logger.Warn(noRulerWarning);
return;
}
if (rulerTerm.PreImperatorRuler.Country is null) {
Logger.Warn(noCountryIdWarning);
return;
}
var countryId = rulerTerm.PreImperatorRuler.Country.Id;
Countries[countryId].RulerTerms.Add(rulerTerm);
if (preImperatorRulerTerms.TryGetValue(countryId, out var list)) {
list.Add(rulerTerm);
} else {
preImperatorRulerTerms[countryId] = new List<RulerTerm> { rulerTerm };
}
});
parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem);
parser.ParseFile(filePath);

foreach (var country in Countries) {
country.RulerTerms = [.. country.RulerTerms.OrderBy(t => t.StartDate)];
}

// verify with data from historical_regnal_numbers
var regnalNameCounts = new Dictionary<ulong, Dictionary<string, int>>(); // <country id, <name, count>>
foreach (var country in Countries) {
if (!preImperatorRulerTerms.ContainsKey(country.Id)) {
continue;
}

regnalNameCounts.Add(country.Id, []);
var countryRulerTerms = regnalNameCounts[country.Id];

foreach (var term in preImperatorRulerTerms[country.Id]) {
if (term.PreImperatorRuler is null) {
Logger.Warn(noRulerWarning);
continue;
}
var name = term.PreImperatorRuler.Name;
if (name is null) {
Logger.Warn("Pre-Imperator ruler has no country name!");
continue;
}
if (countryRulerTerms.TryGetValue(name, out int value)) {
countryRulerTerms[name] = value + 1;
} else {
countryRulerTerms[name] = 1;
}
}
}
foreach (var country in Countries) {
bool equal;
if (!regnalNameCounts.TryGetValue(country.Id, out Dictionary<string, int>? countsForTitle)) {
equal = country.HistoricalRegnalNumbers.Count == 0;
} else {
equal = country.HistoricalRegnalNumbers.OrderBy(kvp => kvp.Key)
.SequenceEqual(countsForTitle.OrderBy(kvp => kvp.Key)
);
}

if (!equal) {
Logger.Debug($"List of pre-Imperator rulers of {country.Tag} doesn't match data from save!");
}
}
}

Check notice on line 634 in ImperatorToCK3/Imperator/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/Imperator/World.cs#L560-L634

Complex Method
private void LoadModFilesystemDependentData() {
// Some stuff can be loaded in parallel to save time.
Parallel.Invoke(
Expand Down Expand Up @@ -795,6 +795,8 @@

private readonly IgnoredKeywordsSet ignoredTokens = [];

[GeneratedRegex(@"\bSAV\w*\b")]
private static partial Regex SaveStartRegex();
[GeneratedRegex(@"^\S+=\s*\{[\s\S]*?^\}", RegexOptions.Multiline)]
private static partial Regex FlagDefinitionRegex();
[GeneratedRegex(@"\$[A-Z_]*\$")]
Expand Down
5 changes: 5 additions & 0 deletions ImperatorToCK3/Mappers/Province/ProvinceMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ private void CreateMappings(ProvinceMappingsVersion mappingsVersion) {
continue;
}

// We don't want many-to-many mappings.
if (mapping.ImperatorProvinces.Count > 1 && mapping.CK3Provinces.Count > 1) {
Logger.Warn($"Many-to-many province mapping found: {string.Join(", ", mapping.ImperatorProvinces)} -> {string.Join(", ", mapping.CK3Provinces)}");
}

foreach (var impNumber in mapping.ImperatorProvinces) {
if (impNumber != 0) {
imperatorToCK3ProvinceMap.Add(impNumber, mapping.CK3Provinces);
Expand Down
Loading