Skip to content

Conversion of Imperator Defensive Leagues + compatches for TFE and RoA #2631

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 9 commits into from
Jun 22, 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
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,21 @@ public void DependencyCanBeLoaded() {
Assert.Equal(new Date("1.1.1", AUC: true), diplomacy.Dependencies[0].StartDate);
Assert.Equal("tributary", diplomacy.Dependencies[0].SubjectType);
}

[Fact]
public void DefensiveLeagueCanBeLoaded() {
var reader = new BufferedReader(
"""
defensive_league={
member=7
member=552
}
""");
var diplomacy = new ImperatorToCK3.Imperator.Diplomacy.DiplomacyDB(reader);

Assert.Single(diplomacy.DefensiveLeagues);
Assert.Equal(2, diplomacy.DefensiveLeagues[0].Count);
Assert.Equal((ulong)7, diplomacy.DefensiveLeagues[0][0]);
Assert.Equal((ulong)552, diplomacy.DefensiveLeagues[0][1]);
}
}
4 changes: 2 additions & 2 deletions ImperatorToCK3/CK3/Cultures/PillarCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ private void LoadPillar(string pillarId, BufferedReader pillarReader, OrderedDic
Logger.Warn($"Language {pillarId} is missing required language_family parameter!");
}
}
if (ck3ModFlags["wtwsms"]) {
if (ck3ModFlags["wtwsms"] || ck3ModFlags["roa"]) {
if (!pillar.Parameters.AsValueEnumerable().Any(p => p.Key.StartsWith("language_branch_"))) {
Logger.Warn($"Language {pillarId} is missing required language_branch parameter!");
}
}
if (ck3ModFlags["tfe"] || ck3ModFlags["roa"]) {
if (ck3ModFlags["tfe"]) {
if (!pillar.Parameters.AsValueEnumerable().Any(p => p.Key.StartsWith("language_group_"))) {
Logger.Warn($"Language {pillarId} is missing required language_group parameter!");
}
Expand Down
37 changes: 37 additions & 0 deletions ImperatorToCK3/CK3/Diplomacy/DiplomacyDB.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using commonItems;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.Imperator.Countries;
using System.Collections.Generic;
using System.Linq;

namespace ImperatorToCK3.CK3.Diplomacy;

internal class DiplomacyDB {
public List<List<Title>> Leagues { get; } = [];

public void ImportImperatorLeagues(IReadOnlyCollection<List<ulong>> irLeagues, CountryCollection countries) {
Logger.Info("Importing Imperator defensive leagues...");

foreach (var irLeague in irLeagues) {
List<Title> ck3LeagueMembers = [];
foreach (var irMemberId in irLeague) {
if (!countries.TryGetValue(irMemberId, out var country)) {
Logger.Warn($"Member {irMemberId} of defensive league not found in countries!");
continue;
}

var ck3Title = country.CK3Title;
if (ck3Title is not null) {
ck3LeagueMembers.Add(ck3Title);
}
}

if (ck3LeagueMembers.Count < 2) {
Logger.Notice("Not enough members in league to import it, skipping: " +
$"{string.Join(", ", ck3LeagueMembers.Select(t => t.Id))}");
continue;
}
Leagues.Add(ck3LeagueMembers);
}
}
}
7 changes: 7 additions & 0 deletions ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using ImperatorToCK3.CK3.Armies;
using ImperatorToCK3.CK3.Characters;
using ImperatorToCK3.CK3.Cultures;
using ImperatorToCK3.CK3.Diplomacy;
using ImperatorToCK3.CK3.Dynasties;
using ImperatorToCK3.CK3.Legends;
using ImperatorToCK3.CK3.Provinces;
Expand Down Expand Up @@ -38,6 +39,7 @@
using System.Threading;
using System.Threading.Tasks;
using Open.Collections;
using DiplomacyDB = ImperatorToCK3.CK3.Diplomacy.DiplomacyDB;
using System.Collections.Frozen;

namespace ImperatorToCK3.CK3;
Expand All @@ -60,6 +62,7 @@
public MapData MapData { get; private set; } = null!;
public List<Wars.War> Wars { get; } = [];
public LegendSeedCollection LegendSeeds { get; } = [];
public DiplomacyDB Diplomacy { get; } = new();
internal CoaMapper CK3CoaMapper { get; private set; } = null!;
private readonly List<string> enabledDlcFlags = [];

Expand All @@ -68,350 +71,354 @@
/// </summary>
public Date CorrectedDate { get; }

public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtractThread) {
Logger.Info("*** Hello CK3, let's get painting. ***");

warMapper.DetectUnmappedWarGoals(impWorld.ModFS);

DetermineCK3Dlcs(config);
LoadAndDetectCK3Mods(config);

// Initialize fields that depend on other fields.
Religions = new ReligionCollection(LandedTitles);

// Determine CK3 bookmark date.
CorrectedDate = impWorld.EndDate.Year > 1 ? impWorld.EndDate : new Date(2, 1, 1);
if (config.CK3BookmarkDate.Year == 0) { // bookmark date is not set
config.CK3BookmarkDate = CorrectedDate;
Logger.Info($"CK3 bookmark date set to: {config.CK3BookmarkDate}");
} else if (CorrectedDate > config.CK3BookmarkDate) {
Logger.Warn($"Corrected save can't be later than CK3 bookmark date, setting CK3 bookmark date to {CorrectedDate}!");
config.CK3BookmarkDate = CorrectedDate;
}

// Recreate output mod folder.
string outputModPath = Path.Join("output", config.OutputModName);
WorldOutputter.ClearOutputModFolder(outputModPath);
WorldOutputter.CreateModFolder(outputModPath);
// This will also convert all Liquid templates into simple text files.
WorldOutputter.CopyBlankModFilesToOutput(outputModPath, config.GetCK3ModFlags());

// Include a fake mod pointing to blankMod in the output folder.
LoadedMods.Add(new Mod("blankMod", outputModPath));
ModFS = new ModFilesystem(Path.Combine(config.CK3Path, "game"), LoadedMods);

var ck3Defines = new Defines();
ck3Defines.LoadDefines(ModFS);

ColorFactory ck3ColorFactory = new();
// Now that we have the mod filesystem, we can initialize the localization database.
Parallel.Invoke(
() => LoadCorrectProvinceMappingsFile(impWorld, config), // Depends on loaded mods.
() => {
LocDB.LoadLocFromModFS(ModFS, config.GetActiveCK3ModFlags());
Logger.IncrementProgress();
},
() => ScriptValues.LoadScriptValues(ModFS, ck3Defines),
() => {
NamedColors.LoadNamedColors("common/named_colors", ModFS);
ck3ColorFactory.AddNamedColorDict(NamedColors);
},
() => {
Logger.Info("Loading map data...");
MapData = new MapData(ModFS);
},
() => CK3CoaMapper = new(ModFS),
() => {
// Modify some CK3 and mod files and put them in the output before we start outputting anything.
FileTweaker.ModifyAndRemovePartsOfFiles(ModFS, outputModPath, config).Wait();
}
);

System.Collections.Generic.OrderedDictionary<string, bool> ck3ModFlags = config.GetCK3ModFlags();

Parallel.Invoke(
() => { // depends on ck3ColorFactory and CulturalPillars
// Load CK3 cultures from CK3 mod filesystem.
Logger.Info("Loading cultural pillars...");
CulturalPillars = new(ck3ColorFactory, ck3ModFlags);
CulturalPillars.LoadPillars(ModFS, ck3ModFlags);
Logger.Info("Loading converter cultural pillars...");
CulturalPillars.LoadConverterPillars("configurables/cultural_pillars", ck3ModFlags);
Cultures = new CultureCollection(ck3ColorFactory, CulturalPillars, ck3ModFlags);
Cultures.LoadNameLists(ModFS);
Cultures.LoadInnovationIds(ModFS);
Cultures.LoadCultures(ModFS, config);
Cultures.LoadConverterCultures("configurables/converter_cultures.txt", config);
Cultures.WarnAboutCircularParents();
Logger.IncrementProgress();
},
() => LoadMenAtArmsTypes(ModFS, ScriptValues), // depends on ScriptValues
() => { // depends on LocDB and CK3CoaMapper
// Load vanilla CK3 landed titles and their history
LandedTitles.LoadTitles(ModFS, LocDB);

if (config.StaticDeJure) {
Logger.Info("Setting static de jure kingdoms and empires...");

Title.LandedTitles overrideTitles = [];
overrideTitles.LoadStaticTitles();
LandedTitles.CarveTitles(overrideTitles);

Logger.IncrementProgress();
}

LandedTitles.SetCoatsOfArms(CK3CoaMapper);

LandedTitles.LoadHistory(config, ModFS);
LandedTitles.LoadCulturalNamesFromConfigurables();
}
);

// Load regions.
ck3RegionMapper = new CK3RegionMapper(ModFS, LandedTitles);
imperatorRegionMapper = impWorld.ImperatorRegionMapper;

CultureMapper cultureMapper = null!;
TraitMapper traitMapper = null!;
DNAFactory dnaFactory = null!;
Parallel.Invoke(
() => { // depends on ck3ColorFactory and landed titles being loaded
// Load CK3 religions from game and blankMod.
// Holy sites need to be loaded after landed titles.
Religions.LoadDoctrines(ModFS);
Logger.Info("Loaded CK3 doctrines.");
Religions.LoadConverterHolySites("configurables/converter_holy_sites.txt");
Logger.Info("Loaded converter holy sites.");
Religions.LoadHolySites(ModFS);
Logger.Info("Loaded CK3 holy sites.");
Logger.Info("Loading religions from CK3 game and mods...");
Religions.LoadReligions(ModFS, ck3ColorFactory);
Logger.Info("Loaded CK3 religions.");
Logger.IncrementProgress();
Logger.Info("Loading converter faiths...");
Religions.LoadConverterFaiths("configurables/converter_faiths.txt", ck3ColorFactory);
Logger.Info("Loaded converter faiths.");
Logger.IncrementProgress();
Religions.RemoveChristianAndIslamicSyncretismFromAllFaiths();
// Now that all the faiths are loaded, remove liege entries from the history of religious head titles.
LandedTitles.RemoveLiegeEntriesFromReligiousHeadHistory(Religions);

Religions.LoadReplaceableHolySites("configurables/replaceable_holy_sites.txt");
Logger.Info("Loaded replaceable holy sites.");
},

() => cultureMapper = new CultureMapper(imperatorRegionMapper, ck3RegionMapper, Cultures),

() => traitMapper = new("configurables/trait_map.txt", ModFS),

() => {
Logger.Info("Initializing DNA factory...");
dnaFactory = new(impWorld.ModFS, ModFS);
Logger.IncrementProgress();
},

() => {
Characters.LoadCK3Characters(ModFS, config.CK3BookmarkDate);
Logger.IncrementProgress();
}
);

var religionMapper = new ReligionMapper(Religions, imperatorRegionMapper, ck3RegionMapper);

Parallel.Invoke(
() => Cultures.ImportTechnology(impWorld.Countries, cultureMapper, provinceMapper, impWorld.InventionsDB, impWorld.LocDB, ck3ModFlags),

() => { // depends on religionMapper
// Check if all I:R religions have a base mapping.
foreach (var irReligionId in impWorld.Religions.Select(r => r.Id)) {
var baseMapping = religionMapper.Match(irReligionId, null, null, null, null, config);
if (baseMapping is null) {
string religionStr = "ID: " + irReligionId;
var localizedName = impWorld.LocDB.GetLocBlockForKey(irReligionId)?["english"];
if (localizedName is not null) {
religionStr += $", name: {localizedName}";
}
Logger.Warn($"No base mapping found for I:R religion {religionStr}!");
}
}
},
() => { // depends on cultureMapper
// Check if all I:R cultures have a base mapping.
var irCultureIds = impWorld.CulturesDB.SelectMany(g => g.Select(c => c.Id));
foreach (var irCultureId in irCultureIds) {
var baseMapping = cultureMapper.Match(irCultureId, null, null, null);
if (baseMapping is null) {
string cultureStr = "ID: " + irCultureId;
var localizedName = impWorld.LocDB.GetLocBlockForKey(irCultureId)?["english"];
if (localizedName is not null) {
cultureStr += $", name: {localizedName}";
}
Logger.Warn($"No base mapping found for I:R culture {cultureStr}!");
}
}
},
() => { // depends on TraitMapper and CK3 characters being loaded
Characters.RemoveUndefinedTraits(traitMapper);
}
);

Characters.ImportImperatorCharacters(
impWorld,
religionMapper,
cultureMapper,
Cultures,
traitMapper,
nicknameMapper,
provinceMapper,
deathReasonMapper,
dnaFactory,
LocDB,
impWorld.EndDate,
config
);
// Now that we have loaded all characters, we can mark some of them as non-removable.
Characters.LoadCharacterIDsToPreserve(config.CK3BookmarkDate);
ClearFeaturedCharactersDescriptions(config.CK3BookmarkDate);

Dynasties.LoadCK3Dynasties(ModFS);
// Now that we have loaded all dynasties from CK3, we can remove invalid dynasty IDs from character history.
Characters.RemoveInvalidDynastiesFromHistory(Dynasties);
Dynasties.ImportImperatorFamilies(impWorld, cultureMapper, impWorld.LocDB, LocDB, CorrectedDate);
DynastyHouses.LoadCK3Houses(ModFS);

// Load existing CK3 government IDs.
Logger.Info("Loading CK3 government IDs...");
var ck3GovernmentIds = new HashSet<string>();
var governmentsParser = new Parser();
governmentsParser.RegisterRegex(CommonRegexes.String, (reader, governmentId) => {
ck3GovernmentIds.Add(governmentId);
ParserHelpers.IgnoreItem(reader);
});
governmentsParser.ParseGameFolder("common/governments", ModFS, "txt", recursive: false, logFilePaths: true);
Logger.IncrementProgress();
GovernmentMapper governmentMapper = new(ck3GovernmentIds);
Logger.IncrementProgress();

// Before we can import Imperator countries and governorships, the I:R CoA extraction thread needs to finish.
irCoaExtractThread?.Join();

SuccessionLawMapper successionLawMapper = new("configurables/succession_law_map.liquid", ck3ModFlags);
List<KeyValuePair<Country, Dependency?>> countyLevelCountries = [];
LandedTitles.ImportImperatorCountries(
impWorld.Countries,
impWorld.Dependencies,
tagTitleMapper,
impWorld.LocDB,
LocDB,
provinceMapper,
impWorld.CoaMapper,
governmentMapper,
successionLawMapper,
definiteFormMapper,
religionMapper,
cultureMapper,
nicknameMapper,
Characters,
CorrectedDate,
config,
countyLevelCountries,
enabledDlcFlags
);

// Now we can deal with provinces since we know to whom to assign them. We first import vanilla province data.
// Some of it will be overwritten, but not all.
Provinces.ImportVanillaProvinces(ModFS, Religions, Cultures);

// Next we import Imperator provinces and translate them ontop a significant part of all imported provinces.
Provinces.ImportImperatorProvinces(impWorld, MapData, LandedTitles, cultureMapper, religionMapper, provinceMapper, CorrectedDate, config);
Provinces.LoadPrehistory();

var countyLevelGovernorships = new List<Governorship>();
LandedTitles.ImportImperatorGovernorships(
impWorld,
Provinces,
tagTitleMapper,
impWorld.LocDB,
LocDB,
config,
provinceMapper,
definiteFormMapper,
imperatorRegionMapper,
impWorld.CoaMapper,
countyLevelGovernorships
);

// Give counties to rulers and governors.
OverwriteCountiesHistory(impWorld.Countries, impWorld.JobsDB.Governorships, countyLevelCountries, countyLevelGovernorships, impWorld.Characters, impWorld.Provinces, CorrectedDate);
// Import holding owners as barons and counts.
LandedTitles.ImportImperatorHoldings(Provinces, impWorld.Characters, impWorld.EndDate);

LandedTitles.ImportDevelopmentFromImperator(Provinces, CorrectedDate, config.ImperatorCivilizationWorth);
LandedTitles.RemoveInvalidLandlessTitles(config.CK3BookmarkDate);

// Apply region-specific tweaks.
HandleIcelandAndFaroeIslands(impWorld, config);

// Check if any muslim religion exists in Imperator. Otherwise, remove Islam from the entire CK3 map.
var possibleMuslimReligionNames = new List<string> { "muslim", "islam", "sunni", "shiite" };
var muslimReligionExists = impWorld.Religions
.Any(r => possibleMuslimReligionNames.Contains(r.Id.ToLowerInvariant()));
if (muslimReligionExists) {
Logger.Info("Found muslim religion in Imperator save, keeping Islam in CK3.");
} else {
RemoveIslam(config);
}
Logger.IncrementProgress();

// Now that Islam has been handled, we can generate filler holders without the risk of making them Muslim.
GenerateFillerHoldersForUnownedLands(Cultures, config);
Logger.IncrementProgress();
if (!config.StaticDeJure) {
LandedTitles.SetDeJureKingdomsAndEmpires(config.CK3BookmarkDate, Cultures, Characters, MapData, LocDB);
}

Dynasties.SetCoasForRulingDynasties(LandedTitles, config.CK3BookmarkDate);

Characters.RemoveEmployerIdFromLandedCharacters(LandedTitles, CorrectedDate);
Characters.PurgeUnneededCharacters(LandedTitles, Dynasties, DynastyHouses, config.CK3BookmarkDate);
// We could convert Imperator character DNA while importing the characters.
// But that'd be wasteful, because some of them are purged. So, we do it now.
Characters.ConvertImperatorCharacterDNA(dnaFactory);

// If there's a gap between the I:R save date and the CK3 bookmark date,
// generate successors for old I:R characters instead of making them live for centuries.
if (config.CK3BookmarkDate.DiffInYears(impWorld.EndDate) > 1) {
Characters.GenerateSuccessorsForOldCharacters(LandedTitles, Cultures, impWorld.EndDate, config.CK3BookmarkDate, impWorld.RandomSeed);
}

// Gold needs to be distributed after characters' successors are generated.
Characters.DistributeCountriesGold(LandedTitles, config);
Characters.ImportLegions(LandedTitles, impWorld.Units, impWorld.Characters, CorrectedDate, unitTypeMapper, MenAtArmsTypes, provinceMapper, LocDB, config);

// After the purging of unneeded characters, we should clean up the title history.
LandedTitles.CleanUpHistory(Characters, config.CK3BookmarkDate);

// Now that the title history is basically done, convert officials as council members and courtiers.
LandedTitles.ImportImperatorGovernmentOffices(impWorld.JobsDB.OfficeJobs, Religions, impWorld.EndDate);

Parallel.Invoke(
() => ImportImperatorWars(impWorld, config.CK3BookmarkDate),

() => {
var holySiteEffectMapper = new HolySiteEffectMapper("configurables/holy_site_effect_mappings.txt");
Religions.DetermineHolySites(Provinces, impWorld.Religions, holySiteEffectMapper, config.CK3BookmarkDate);

Religions.GenerateMissingReligiousHeads(LandedTitles, Characters, Provinces, Cultures, config.CK3BookmarkDate);
Logger.IncrementProgress();
},

() => {
LegendSeeds.LoadSeeds(ModFS);
LegendSeeds.RemoveAnachronisticSeeds("configurables/legend_seeds_to_remove.txt");
},

() => {
Diplomacy.ImportImperatorLeagues(impWorld.DefensiveLeagues, impWorld.Countries);
}
);
}

Check notice on line 421 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L74-L421

Complex Method
private void LoadAndDetectCK3Mods(Configuration config) {
Logger.Info("Detecting selected CK3 mods...");
List<Mod> incomingCK3Mods = new();
Expand Down Expand Up @@ -507,56 +514,56 @@
}
}

private void OverwriteCountiesHistory(CountryCollection irCountries, List<Governorship> governorships, List<KeyValuePair<Country, Dependency?>> countyLevelCountries, List<Governorship> countyLevelGovernorships, Imperator.Characters.CharacterCollection impCharacters, Imperator.Provinces.ProvinceCollection irProvinces, Date conversionDate) {
Logger.Info("Overwriting counties' history...");
FrozenSet<Governorship> governorshipsSet = governorships.ToFrozenSet();
FrozenSet<Governorship> countyLevelGovernorshipsSet = countyLevelGovernorships.ToFrozenSet();

foreach (var county in LandedTitles.Where(t => t.Rank == TitleRank.county)) {
if (county.CapitalBaronyProvinceId is null) {
Logger.Warn($"County {county} has no capital barony province!");
continue;
}
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!");
continue;
}

if (!Provinces.ContainsKey(capitalBaronyProvinceId)) {
Logger.Warn($"Capital barony province not found: {capitalBaronyProvinceId}");
continue;
}

var ck3CapitalBaronyProvince = Provinces[capitalBaronyProvinceId];
var irProvince = ck3CapitalBaronyProvince.PrimaryImperatorProvince;
if (irProvince is null) { // probably outside of Imperator map
continue;
}

var irCountry = irProvince.OwnerCountry;

if (irCountry is null || irCountry.CountryType == CountryType.rebels) { // e.g. uncolonized Imperator province
county.SetHolder(null, conversionDate);
county.SetDeFactoLiege(null, conversionDate);
RevokeBaroniesFromCountyGivenToImperatorCharacter(county);
} else {
bool given = TryGiveCountyToCountyLevelRuler(county, irCountry, countyLevelCountries, irCountries);
if (!given) {
given = TryGiveCountyToGovernor(county, irProvince, irCountry, governorshipsSet, irProvinces, countyLevelGovernorshipsSet, impCharacters);
}
if (!given) {
given = TryGiveCountyToMonarch(county, irCountry);
}
if (!given) {
Logger.Warn($"County {county} was not given to anyone!");
}
}
}
Logger.IncrementProgress();
}

Check notice on line 566 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L517-L566

Complex Method
private bool TryGiveCountyToMonarch(Title county, Country irCountry) {
var ck3Country = irCountry.CK3Title;
if (ck3Country is null) {
Expand All @@ -569,58 +576,58 @@
return true;
}

private bool TryGiveCountyToGovernor(Title county,
Imperator.Provinces.Province irProvince,
Country irCountry,
FrozenSet<Governorship> governorshipsSet,
Imperator.Provinces.ProvinceCollection irProvinces,
FrozenSet<Governorship> countyLevelGovernorshipsSet,
Imperator.Characters.CharacterCollection irCharacters) {
var ck3Country = irCountry.CK3Title;
if (ck3Country is null) {
Logger.Warn($"{irCountry.Name} has no CK3 title!"); // should not happen
return false;
}
var matchingGovernorships = new List<Governorship>(governorshipsSet.Where(g =>
g.Country.Id == irCountry.Id &&
g.Region.Id == imperatorRegionMapper.GetParentRegionName(irProvince.Id)
));

var ck3CapitalCounty = ck3Country.CapitalCounty;
if (ck3CapitalCounty is null) {
var logLevel = ck3Country.ImperatorCountry?.PlayerCountry == true ? Level.Warn : Level.Debug;
Logger.Log(logLevel, $"{ck3Country} has no capital county!");
return false;
}
// if title belongs to country ruler's capital's de jure duchy, it needs to be directly held by the ruler
var countryCapitalDuchy = ck3CapitalCounty.DeJureLiege;
var deJureDuchyOfCounty = county.DeJureLiege;
if (countryCapitalDuchy is not null && deJureDuchyOfCounty is not null && countryCapitalDuchy.Id == deJureDuchyOfCounty.Id) {
return false;
}

if (matchingGovernorships.Count == 0) {
// we have no matching governorship
return false;
}

// give county to governor
var governorship = matchingGovernorships[0];
var ck3GovernorshipId = tagTitleMapper.GetTitleForGovernorship(governorship, LandedTitles, irProvinces, Provinces, imperatorRegionMapper, provinceMapper);
if (ck3GovernorshipId is null) {
Logger.Warn($"{nameof(ck3GovernorshipId)} is null for {ck3Country} {governorship.Region.Id}!");
return false;
}

if (countyLevelGovernorshipsSet.Contains(governorship)) {
GiveCountyToCountyLevelGovernor(county, governorship, ck3Country, irCharacters);
} else {
GiveCountyToGovernor(county, ck3GovernorshipId);
}
RevokeBaroniesFromCountyGivenToImperatorCharacter(county);
return true;
}

Check notice on line 630 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L579-L630

Complex Method
private void GiveCountyToMonarch(Title county, Title ck3Country) {
var date = ck3Country.GetDateOfLastHolderChange();
var holderId = ck3Country.GetHolderId(date);
Expand Down Expand Up @@ -698,113 +705,113 @@
}
}

private void HandleIcelandAndFaroeIslands(Imperator.World irWorld, Configuration config) {
Logger.Info("Handling Iceland and Faroe Islands...");
Date bookmarkDate = config.CK3BookmarkDate;
var year = bookmarkDate.Year;

var faiths = Religions.Faiths.ToArray();

OrderedSet<string> titleIdsToHandle;
if (config.FallenEagleEnabled) {
// Iceland doesn't exist on TFE map.
titleIdsToHandle = ["c_faereyar"];
} else if (irWorld.TerraIndomitaDetected) {
// The Faroe Islands are on the map in TI, so it should be handled normally instead of being given an Eremitic holder.
titleIdsToHandle = ["d_iceland"];
} else {
titleIdsToHandle = ["d_iceland", "c_faereyar"];
}

bool generateHermits = true;
IEnumerable<string> faithCandidates = new OrderedSet<string>();
Queue<string> namePool = new();
const string defaultCultureId = "irish";
string cultureId = defaultCultureId;

switch (year) {
case <= 300:
UsePaganRulers();
break;
case < 874:
faithCandidates = new OrderedSet<string> { "insular_celtic", "catholic", "orthodox", "chalcedonian", "nicene" };
var christianFaiths = Religions.TryGetValue("christianity_religion", out var christianityReligion) ? christianityReligion.Faiths : [];

// If there is at least one Irish Christian county, give it to the Irish Papar.
// If there is at least one Christian county of another Gaelic culture, give it to a character of this Gaelic culture.
var cultureCandidates = new[] { "irish", "gaelic" };
bool provinceFound = false;
foreach (var potentialCultureId in cultureCandidates) {
var cultureProvinces = Provinces.Where(p =>
p.GetCultureId(bookmarkDate) == potentialCultureId);
foreach (var cultureProvince in cultureProvinces) {
var faithId = cultureProvince.GetFaithId(bookmarkDate);
if (faithId is null || !christianFaiths.ContainsKey(faithId)) {
continue;
}
provinceFound = true;
cultureId = potentialCultureId;
faithCandidates = faithCandidates.Prepend(faithId);
break;
}
if (provinceFound) {
break;
}
}
if (!provinceFound) {
// If all the Gaels are pagan but at least one province in Ireland or Scotland is Christian,
// give the handled titles to a generated ruler of the same culture as that Christian province.
var potentialSourceProvinces = Provinces.Where(p =>
ck3RegionMapper.ProvinceIsInRegion(p.Id, "custom_ireland") || ck3RegionMapper.ProvinceIsInRegion(p.Id, "custom_scotland"));
foreach (var potentialSourceProvince in potentialSourceProvinces) {
var faithId = potentialSourceProvince.GetFaithId(bookmarkDate);
if (faithId is null || !christianFaiths.ContainsKey(faithId)) {
continue;
}
provinceFound = true;
cultureId = potentialSourceProvince.GetCultureId(bookmarkDate) ?? defaultCultureId;
faithCandidates = faithCandidates.Prepend(faithId);
break;
}
}
if (!provinceFound) {
// Give up and create a pagan ruler.
UsePaganRulers();
} else {
Logger.Info("Giving Iceland and Faroe Islands to Papar...");
namePool = new Queue<string>(["Canann", "Petair", "Fergus"]);
}
break;
default:
Logger.Info("Keeping Iceland and Faroe Islands as is in history...");
// Let CK3 use rulers from its history.
generateHermits = false;
break;
}

if (generateHermits) {
var faithId = faithCandidates.First(c => faiths.Any(f => f.Id == c));
foreach (var titleId in titleIdsToHandle) {
if (!LandedTitles.TryGetValue(titleId, out var title)) {
Logger.Warn($"Title {titleId} not found!");
continue;
}

GenerateHermitForTitle(title, namePool, bookmarkDate, faithId, cultureId, config);
}
}

Logger.IncrementProgress();

void UsePaganRulers() {
Logger.Info("Giving Iceland and Faroe Islands to pagan Gaels...");
faithCandidates = new OrderedSet<string> { "gaelic_paganism", "celtic_pagan", "briton_paganism", "pagan" };
cultureId = "gaelic";
// ReSharper disable once StringLiteralTypo
namePool = new Queue<string>(["A_engus", "Domnall", "Rechtabra"]);
}
}

Check notice on line 814 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L708-L814

Complex Method
private void GenerateHermitForTitle(Title title, Queue<string> namePool, Date bookmarkDate, string faithId, string cultureId, Configuration config) {
Logger.Debug($"Generating hermit for {title.Id}...");

Expand Down Expand Up @@ -958,161 +965,161 @@
}
}

private void GenerateFillerHoldersForUnownedLands(CultureCollection cultures, Configuration config) {
Logger.Info("Generating filler holders for unowned lands...");
var date = config.CK3BookmarkDate;
List<Title> unheldCounties = [];
foreach (var county in LandedTitles.Counties) {
var holderId = county.GetHolderId(date);
if (holderId == "0") {
unheldCounties.Add(county);
} else if (Characters.TryGetValue(holderId, out var holder)) {
if (holder.DeathDate is not null && holder.DeathDate <= date) {
Logger.Debug($"Adding {county.Id} to unheld counties because holder {holderId} is dead.");
unheldCounties.Add(county);
}
}
}

var duchyIdToHolderDict = new Dictionary<string, Character>();

foreach (var county in unheldCounties) {
if (config.FillerDukes) {
var duchy = county.DeJureLiege;
if (duchy is not null && duchy.Rank == TitleRank.duchy) {
if (duchyIdToHolderDict.TryGetValue(duchy.Id, out var duchyHolder)) {
county.SetHolder(duchyHolder, date);
continue;
}
}
}

var candidateProvinces = new OrderedSet<Province>();
if (county.CapitalBaronyProvinceId is not null) {
// Give priority to capital province.
if (Provinces.TryGetValue(county.CapitalBaronyProvinceId.Value, out var capitalProvince)) {
candidateProvinces.Add(capitalProvince);
}
}

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

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
.Select(p => p.GetCulture(date, cultures))
.FirstOrDefault(c => c is not null);
if (culture is null) {
Logger.Debug($"Trying to use de jure duchy for culture of holder for {county.Id}...");
var deJureDuchy = county.DeJureLiege;
if (deJureDuchy is not null) {
culture = Provinces
.Where(p => deJureDuchy.DuchyContainsProvince(p.Id))
.Select(p => p.GetCulture(date, cultures))
.FirstOrDefault(c => c is not null);
}
if (culture is null && deJureDuchy?.DeJureLiege is not null) {
Logger.Debug($"Trying to use de jure kingdom for culture of holder for {county.Id}...");
var deJureKingdom = deJureDuchy.DeJureLiege;
culture = Provinces
.Where(p => deJureKingdom.KingdomContainsProvince(p.Id))
.Select(p => p.GetCulture(date, cultures))
.FirstOrDefault(c => c is not null);
}
if (culture is null) {
Logger.Warn($"Found no fitting culture for generated holder of {county.Id}, " +
"using first culture from database!");
culture = cultures.First();
}
}

// Determine faith of the holder.
var faithId = candidateProvinces
.Select(p => p.GetFaithId(date))
.FirstOrDefault(f => f is not null);
if (faithId is null) {
Logger.Debug($"Trying to use de jure duchy for faith of holder for {county.Id}...");
var deJureDuchy = county.DeJureLiege;
if (deJureDuchy is not null) {
faithId = Provinces
.Where(p => deJureDuchy.DuchyContainsProvince(p.Id))
.Select(p => p.GetFaithId(date))
.FirstOrDefault(f => f is not null);
}
if (faithId is null && deJureDuchy?.DeJureLiege is not null) {
Logger.Debug($"Trying to use de jure kingdom for faith of holder for {county.Id}...");
var deJureKingdom = deJureDuchy.DeJureLiege;
faithId = Provinces
.Where(p => deJureKingdom.KingdomContainsProvince(p.Id))
.Select(p => p.GetFaithId(date))
.FirstOrDefault(f => f is not null);
}
if (faithId is null) {
Logger.Warn($"Found no fitting faith for generated holder of {county.Id}, " +
"using first faith from database!");
faithId = Religions.Faiths.First().Id;
}
}

bool female = false;
string name;
var maleNames = culture.MaleNames.ToImmutableList();
if (maleNames.Count > 0) {
name = maleNames[pseudoRandomSeed % maleNames.Count];
} else { // Generate a female if no male name is available.
female = true;
var femaleNames = culture.FemaleNames.ToImmutableList();
name = femaleNames[pseudoRandomSeed % femaleNames.Count];
}
int age = 18 + (pseudoRandomSeed % 60);
var holder = new Character($"IRToCK3_{county.Id}_holder", name, date, Characters) {
FromImperator = true,
Female = female,
BirthDate = date.ChangeByYears(-age)
};
holder.SetFaithId(faithId, null);
holder.SetCultureId(culture.Id, null);
holder.History.AddFieldValue(holder.BirthDate, "effects", "effect", "{ set_variable = irtock3_uncolonized_filler }");
Characters.AddOrReplace(holder);

var countyHoldingTypes = county.CountyProvinceIds
.Select(id => Provinces.TryGetValue(id, out var province) ? province : null)
.Where(p => p is not null)
.Select(p => p!.GetHoldingType(date))
.Where(t => t is not null)
.Select(t => t!)
.ToFrozenSet();
string government = countyHoldingTypes.Contains("castle_holding")
? "feudal_government"
: "tribal_government";

county.SetHolder(holder, date);
if (config.FillerDukes) {
var duchy = county.DeJureLiege;
if (duchy is null || duchy.Rank != TitleRank.duchy) {
continue;
}

duchy.SetHolder(holder, date);
duchy.SetGovernment(government, date);
duchyIdToHolderDict[duchy.Id] = holder;
} else {
county.SetGovernment(government, date);
}
}
}

Check warning on line 1122 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L968-L1122

Very Complex Method
private void DetermineCK3Dlcs(Configuration config) {
var dlcFolderPath = Path.Join(config.CK3Path, "game/dlc");
if (!Directory.Exists(dlcFolderPath)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on_game_start = {
on_actions = {
IRToCK3_create_admin_noble_families
IRToCK3_fix_roman_empire_name
IRToCK3_initial_variables
}
}

Expand Down Expand Up @@ -723,4 +724,11 @@ IRToCK3_gamestart_events = {
trigger_event = welcome.1 # This event so far just notifies the players of the game rules added by the converter
}
}
}

# This just sets up a global variable that could be checked by any theoretical mod that wants to consider whether the converter was used
IRToCK3_initial_variables = {
effect = {
set_global_variable = irtock3_enabled
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
## This handles setting up the defensive leagues converted from Imperator
# Inputs:
# list = irtock3_confederation_members - A list containing all of the members of the specific Imperator Defensive League
irtock3_confederation_setup_effect = {
if = { # Check if the Confederations & Leagues mod is loaded, allowing more than Nomads/Tribes into confederations
limit = { has_global_variable = confed_league_enabled }

if = {
limit = { # Need to make sure that there are enough members in the Imperator Defensive League of appropriate rank. Confederations & Leagues might be modified to allow smaller kingdoms into Confederations/Leagues (likely as a game rule), so when that is done, this will likely get modified to account for that.
any_in_list = {
list = irtock3_confederation_members
count > 1
CL_has_appropriate_title_tier = yes
}
}

# Remove rulers who can't be in a confederation/league because of title rank or government, so they aren't added later or considered for anything else later on
every_in_list = {
limit = {
OR = {
CL_has_appropriate_title_tier = no
AND = {
CL_uses_confederations = no
CL_uses_leagues = no
}
}
}
list = irtock3_confederation_members
remove_from_list = irtock3_confederation_members
}

## Need to determine whether this specific Imperator Defensive League will convert to a Confederation or a League, so check the government of every member, and count how many should be in confederations and how many should be in leagues
set_global_variable = {
name = confed_num
value = 0
}
set_global_variable = {
name = league_num
value = 0
}
every_in_list = {
list = irtock3_confederation_members

if = {
limit = { CL_uses_confederations = yes }
change_global_variable = {
name = confed_num
add = 1
}
}
else_if = {
limit = { CL_uses_leagues = yes }
change_global_variable = {
name = league_num
add = 1
}
}
}

# Now need to get relevant rulers to be used to determine the name of the confederation/league. For now, will just take the two with the largest military strength
if = {
limit = { global_var:league_num >= global_var:confed_num } # This should imply they should be in a Defensive League

# Get first member, for the 'actor' scope
ordered_in_list = {
limit = { CL_uses_leagues = yes }
alternative_limit = { always = yes } # Incase somehow this was chosen and no one is actually capable of using leagues, need to make sure someone is chosen
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = actor
save_scope_as = confederation_offerer
}
# Get second member, for the 'recipient' scope
ordered_in_list = {
limit = {
CL_uses_leagues = yes
NOT = { this = scope:actor }
}
alternative_limit = { # Incase somehow this was chosen and no one is actually capable of using leagues, need to make sure someone is chosen
NOT = { this = scope:actor }
}
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = recipient
save_scope_as = confederation_accepter
}

# When an Imperator Defensive League is converted, its members will always be allowed into the confederation/league, regardless of government, so need to give them a variable that signifies they should be allowed in
every_in_list = {
limit = { CL_uses_leagues = no }
list = irtock3_confederation_members
set_variable = allowed_in_leagues
}
}
else = { # Otherwise, they will be put into a confederation
# Get first member, for the 'actor' scope
ordered_in_list = {
limit = { CL_uses_confederations = yes }
alternative_limit = { always = yes } # Incase somehow this was chosen an no one is actually capable of using confederations, need to make sure someone is chosen
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = actor
save_scope_as = confederation_offerer
}
# Get second member, for the 'recipient' scope
ordered_in_list = {
limit = {
CL_uses_confederations = yes
NOT = { this = scope:actor }
}
alternative_limit = { # Incase somehow this was chosen an no one is actually capable of using confederations, need to make sure someone is chosen
NOT = { this = scope:actor }
}
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = recipient
save_scope_as = confederation_accepter
}

# When an Imperator Defensive League is converted, its members will always be allowed into the confederation/league, regardless of government (as long as they meet the rank requirements), so need to give them a variable that signifies they should be allowed in
every_in_list = {
limit = { CL_uses_confederations = no }
list = irtock3_confederation_members
set_variable = allowed_in_confederations
}
}

# Trigger the event that will create the confederation/league and have both scope:actor and scope:recipient added to it
scope:actor = {
#Event distributor event
trigger_event = mpo_interactions_events.0001
}

# Add other Defensive League members
every_in_list = {
limit = { is_confederation_member = no }
list = irtock3_confederation_members
save_scope_as = new_member
scope:actor.confederation = { add_confederation_member = scope:new_member }
clear_saved_scope = new_member
}

# Cleanup so it doesn't cause any issues when converting multiple Defensive Leagues from Imperator
remove_global_variable = league_num
remove_global_variable = confed_num
clear_saved_scope = actor
clear_saved_scope = confederation_offerer
clear_saved_scope = recipient
clear_saved_scope = confederation_accepter
clear_saved_scope = new_confederation
}
}
# Otherwise, assume just using base game confederations, meaning only Nomads/Tribes should be allowed in
else = {
if = {
limit = { # Need to make sure that there are enough members in the Imperator Defensive League of appropriate rank and government for base game confederations
any_in_list = {
list = irtock3_confederation_members
count > 1
AND = {
highest_held_title_tier < tier_kingdom
OR = {
has_government = nomad_government
has_government = tribal_government
}
}
}
}

# Remove rulers who can't be in a confederation because of title rank or government, so they aren't added later on
every_in_list = {
limit = {
OR = {
highest_held_title_tier >= tier_kingdom
NOR = {
has_government = nomad_government
has_government = tribal_government
}
}
}
list = irtock3_confederation_members
remove_from_list = irtock3_confederation_members
}

## Now need to get relevant rulers to be used to determine the name of the confederation. For now, will just take the two with the largest military strength
# Get first member, for the 'actor' scope
ordered_in_list = {
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = actor
save_scope_as = confederation_offerer
}
# Get second member, for the 'recipient' scope
ordered_in_list = {
limit = {
NOT = { this = scope:actor }
}
list = irtock3_confederation_members
order_by = max_military_strength
save_scope_as = recipient
save_scope_as = confederation_accepter
}

# Trigger the event that will create the confederation and have both scope:actor and scope:recipient added to it
scope:actor = {
#Event distributor event
trigger_event = mpo_interactions_events.0001
}

# Add other Defensive League members
every_in_list = {
limit = { is_confederation_member = no }
list = irtock3_confederation_members
save_scope_as = new_member
scope:actor.confederation = { add_confederation_member = scope:new_member }
clear_saved_scope = new_member
}

# Cleanup so it doesn't cause any issues when converting multiple Defensive Leagues from Imperator
clear_saved_scope = actor
clear_saved_scope = confederation_offerer
clear_saved_scope = recipient
clear_saved_scope = confederation_accepter
clear_saved_scope = new_confederation
}
}

# Empty the list of members so there are no conflicts when converting multiple Imperator Defensive Leagues
every_in_list = {
list = irtock3_confederation_members
remove_from_list = irtock3_confederation_members
}
}
Loading
Loading