Skip to content

Restore support for the Asia Expansion Project mod #2614

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 1 commit into from
May 24, 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
3 changes: 3 additions & 0 deletions ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,350 +67,350 @@
/// </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");
}
);
}

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L70-L413

Complex Method
private void LoadAndDetectCK3Mods(Configuration config) {
Logger.Info("Detecting selected CK3 mods...");
List<Mod> incomingCK3Mods = new();
Expand Down Expand Up @@ -460,10 +460,13 @@
bool irHasTI = irWorld.TerraIndomitaDetected;

bool ck3HasRajasOfAsia = config.RajasOfAsiaEnabled;
bool ck3HasAEP = config.AsiaExpansionProjectEnabled;

string mappingsToUse;
if (irHasTI && ck3HasRajasOfAsia) {
mappingsToUse = "terra_indomita_to_rajas_of_asia";
} else if (irHasTI && ck3HasAEP) {
mappingsToUse = "terra_indomita_to_aep";
} else if (irWorld.InvictusDetected) {
mappingsToUse = "imperator_invictus";
} else {
Expand Down Expand Up @@ -503,56 +506,56 @@
}
}

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

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 558 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L509-L558

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

private bool TryGiveCountyToGovernor(Title county,
Imperator.Provinces.Province irProvince,
Country irCountry,
HashSet<Governorship> governorshipsSet,
Imperator.Provinces.ProvinceCollection irProvinces,
HashSet<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 622 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L571-L622

Complex Method
private void GiveCountyToMonarch(Title county, Title ck3Country) {
var date = ck3Country.GetDateOfLastHolderChange();
var holderId = ck3Country.GetHolderId(date);
Expand Down Expand Up @@ -954,161 +957,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!)
.ToHashSet();
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 1114 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L960-L1114

Very Complex Method
private void DetermineCK3Dlcs(Configuration config) {
var dlcFolderPath = Path.Join(config.CK3Path, "game/dlc");
if (!Directory.Exists(dlcFolderPath)) {
Expand Down
9 changes: 6 additions & 3 deletions ImperatorToCK3/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public sealed class Configuration {
public bool FallenEagleEnabled { get; private set; }
public bool WhenTheWorldStoppedMakingSenseEnabled { get; private set; }
public bool RajasOfAsiaEnabled { get; private set; }
public bool AsiaExpansionProjectEnabled { get; private set; }

public bool OutputCCUParameters => WhenTheWorldStoppedMakingSenseEnabled || FallenEagleEnabled || RajasOfAsiaEnabled;

Expand Down Expand Up @@ -319,16 +320,17 @@ public void DetectSpecificCK3Mods(ICollection<Mod> loadedMods) {
WhenTheWorldStoppedMakingSenseEnabled = true;
Logger.Info($"WtWSMS detected: {wtwsmsMod.Name}");
}

var roaMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("Rajas of Asia", StringComparison.Ordinal));
if (roaMod is not null) {
RajasOfAsiaEnabled = true;
Logger.Info($"RoA detected: {roaMod.Name}");
}

var aepMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("Asia Expansion Project", StringComparison.Ordinal));
if (aepMod is not null) {
throw new UserErrorException("Asia Expansion Project is no longer supported because it's not updated for the current version of CK3. See AEP's description on Steam Workshop.");
AsiaExpansionProjectEnabled = true;
Logger.Info($"AEP detected: {aepMod.Name}");
}
}

Expand All @@ -338,6 +340,7 @@ public OrderedDictionary<string, bool> GetCK3ModFlags() {
["tfe"] = FallenEagleEnabled,
["wtwsms"] = WhenTheWorldStoppedMakingSenseEnabled,
["roa"] = RajasOfAsiaEnabled,
["aep"] = AsiaExpansionProjectEnabled,
};

flags["vanilla"] = !flags.Any(f => f.Value);
Expand Down
21 changes: 19 additions & 2 deletions ImperatorToCK3/Data_Files/configurables/converter_cultures.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ qin = {
}

MOD_DEPENDENT = {
IF roa = {
IF aep = {
ethnicities = {
10 = east_asian_chinese_north
}
}
ELSE_IF roa = {
# copied from han
ethnicities = {
2 = east_asian_han_1
Expand All @@ -127,7 +132,19 @@ qin = {
}

MOD_DEPENDENT = {
IF tfe = {
IF aep = {
coa_gfx = {
chinese_group_coa_gfx
}
building_gfx = {
chinese_building_gfx
}
clothing_gfx = {
chinese_clothing_gfx
}
unit_gfx = { chinese_unit_gfx }
}
ELSE_IF tfe = {
# copied from han
coa_gfx = { chinese_group_coa_gfx }
building_gfx = { chinese_building_gfx indian_building_gfx }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ heritage_palaungic = {
heritage_mon_khmer = { # from Rajas of Asia
REPLACED_BY = {
roa = { heritage_mon_khmer }
aep = { heritage_khmer }
wtwsms = { heritage_burman } # In WtWSMS, heritage_burman is localized as "Southeast Asian"
}
type = heritage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Supported CK3 mod flags for conditional blocks:
# tfe (The Fallen Eagle)
# wtwsms (When the World Stopped Making Sense)
# aep (Asia Expansion Project)
# vanilla (Vanilla CK3)


Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# This file contains blocks from the Asia Expansion Project files that can be removed.
# The structure is as follows:

# <file name> = {
# {
# # comments are supported inside
# some = code
# }
# {
# some code
# some code with other indent
# }
# }

# INDENTATION IS IMPORTANT!
# ASIDE FROM THE CURLY BRACKETS SURROUNDING THE BLOCK, IT MUST MATCH THE ORIGINAL FILE.
# OTHERWISE THE BLOCK WON'T BE REMOVED!


"common/scripted_character_templates/AEP_pool_repopulate_local_flavor.txt" = {
# Prevent random Muslim characters being generated.
{
else_if = {
limit = {
OR = {
culture = culture:butr
culture = culture:zaghawa
}
}
random_list = {
10 = {
set_character_faith = faith:ibadi
}
10 = {
set_character_faith = faith:sufri
}
}
}
}
{
else_if = {
limit = {
OR = {
culture = culture:bolghar
culture = culture:somali
}
}
set_character_faith = faith:ashari
if = {
limit = {
root.capital_county.faith = {
religion_tag = islam_religion
}
}
set_character_faith = root.capital_county.faith
}
}
}

# Prevent random Christian characters being generated.
{
2 = {
trigger = {
root.capital_province.faith = {
religion_tag = christianity_religion
}
root.capital_province = {
geographical_region = world_europe_west_iberia
}
}
set_character_faith = faith:conversos
}
}
{
else_if = {
limit = {
culture = culture:greek
}
random_list = {
10 = {
set_character_faith = faith:iconoclast
}
10 = {
set_character_faith = faith:paulician
}
10 = {
set_character_faith = faith:bogomilist
}
}
}
}
{
5 = {
set_character_faith = faith:nestorian
}
}
{
else_if = {
limit = {
culture = culture:assyrian
}
set_character_faith = faith:nestorian
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This file contains blocks from the Asia Expansion Project files that can be replaced with new ones.
# The structure is as follows:

# <file name> = {
# replace = {
# before = {
# some original code
# }
# after = {
# some modified code
# }
# }
#
# replace = {
# before = {
# some original code 2
# }
# after = {
# some modified code 2
# }
# }
# }

# INDENTATION IS IMPORTANT INSIDE the before BLOCK!
# ASIDE FROM THE CURLY BRACKETS SURROUNDING THE BLOCK, IT MUST MATCH THE ORIGINAL FILE.
# OTHERWISE THE BLOCKS WON'T BE MODIFIED!

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Supported CK3 mod flags for conditional blocks:
# tfe (The Fallen Eagle)
# wtwsms (When the World Stopped Making Sense)
# aep (Asia Expansion Project)
# vanilla (Vanilla CK3)


Expand Down
1 change: 1 addition & 0 deletions ImperatorToCK3/Data_Files/converter_globals/FAQ.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ A: The converter officially supports these Imperator mods:
The converter also supports these CK3 mods:
- The Fallen Eagle (https://steamcommunity.com/sharedfiles/filedetails/?id=2243307127)
- When the World Stopped Making Sense (https://steamcommunity.com/sharedfiles/filedetails/?id=2858562094)
- Asia Expansion Project (https://steamcommunity.com/workshop/filedetails/?id=2970440958)
As for other mods, unless they change the map or how cultures, religions or flags work, you can probably use them.
Total map overhauls are not supported (of course), and whatever new cultures and religions are brought by the mod - you'll have to add manually in the files in configurables folder.

Expand Down
7 changes: 7 additions & 0 deletions ImperatorToCK3/Outputter/BookmarkOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,92 +58,99 @@
await using var output = FileHelper.OpenWriteWithRetries(path, Encoding.UTF8);
await output.WriteAsync(sb.ToString());

if (config.AsiaExpansionProjectEnabled) {
// Remove the AEP bookmarks.
var dummyAEPBookmarksOutputPath = Path.Combine("output", config.OutputModName, "common/bookmarks/bookmarks/00_AEP_bookmarks.txt");
await using var dummyAEPBookmarksOutput = FileHelper.OpenWriteWithRetries(dummyAEPBookmarksOutputPath, Encoding.UTF8);
await dummyAEPBookmarksOutput.WriteAsync("# IRToCK3: Removed AEP bookmarks.");
}

await DrawBookmarkMap(config, playerTitles, world);
Logger.IncrementProgress();
}

private static async Task AddTitleToBookmarkScreen(
Title title,
StringBuilder sb,
string holderId,
World world,
CK3LocDB ck3LocDB,
IReadOnlyDictionary<ulong, ProvincePosition> provincePositions,
Configuration config
) {
var holder = world.Characters[holderId];

// Add character localization for bookmark screen.
var holderLoc = ck3LocDB.GetOrCreateLocBlock($"bm_converted_{holder.Id}");
string? holderNameKey = holder.GetName(config.CK3BookmarkDate);
if (holderNameKey is not null) {
if (ck3LocDB.TryGetValue(holderNameKey, out var holderNameLoc)) {
holderLoc.CopyFrom(holderNameLoc);
} else {
// Use the raw name key.
foreach (var language in ConverterGlobals.SupportedLanguages) {
holderLoc[language] = holderNameKey;
}
}
}
var holderDescLoc = ck3LocDB.GetOrCreateLocBlock($"bm_converted_{holder.Id}_desc");
foreach (var language in ConverterGlobals.SupportedLanguages) {
holderDescLoc[language] = string.Empty;
}

sb.AppendLine("\tcharacter = {");

sb.AppendLine($"\t\tname = bm_converted_{holder.Id}");
var dynastyId = holder.GetDynastyId(config.CK3BookmarkDate);
if (dynastyId is not null) {
sb.AppendLine($"\t\tdynasty = {dynastyId}");
}
sb.AppendLine("\t\tdynasty_splendor_level = 1");
sb.AppendLine($"\t\ttype = {holder.GetAgeSex(config.CK3BookmarkDate)}");
sb.AppendLine($"\t\thistory_id = {holder.Id}");
sb.AppendLine($"\t\tbirth = {holder.BirthDate}");
sb.AppendLine($"\t\ttitle = {title.Id}");
var gov = title.GetGovernment(config.CK3BookmarkDate);
if (gov is not null) {
sb.AppendLine($"\t\tgovernment = {gov}");
}

sb.AppendLine($"\t\tculture = {holder.GetCultureId(config.CK3BookmarkDate)}");
var faithId = holder.GetFaithId(config.CK3BookmarkDate);
if (!string.IsNullOrEmpty(faithId)) {
sb.AppendLine($"\t\treligion={faithId}");
}
sb.AppendLine("\t\tdifficulty = \"BOOKMARK_CHARACTER_DIFFICULTY_EASY\"");
WritePosition(sb, title, config, provincePositions);
sb.AppendLine("\t\tanimation = personality_rational");

sb.AppendLine("\t}");

var agesex = holder.GetAgeSex(config.CK3BookmarkDate);

StringBuilder portraitBuilder = new();
portraitBuilder.AppendLine($"bm_converted_{holder.Id} = {{");
portraitBuilder.AppendLine($"\ttype = {agesex}");
portraitBuilder.AppendLine($"\tage = 0.{holder.GetAge(config.CK3BookmarkDate)}");
portraitBuilder.AppendLine("\tgenes = {");
var genesStr = holder.DNA is not null ? string.Join('\n', holder.DNA.DNALines) : string.Empty;
portraitBuilder.AppendLine("\t\t" + genesStr);
portraitBuilder.AppendLine("\t}");
portraitBuilder.AppendLine($"\tentity = {{ {agesexToEntityDict[agesex]} }}");
portraitBuilder.Append('}');

var outPortraitPath = Path.Combine("output", config.OutputModName, $"common/bookmark_portraits/bm_converted_{holder.Id}.txt");
await File.WriteAllTextAsync(outPortraitPath, portraitBuilder.ToString());
}

// Not sure what is the purpose of these values, but all vanilla bookmark portraits have entity entries.
private static readonly Dictionary<string, string> agesexToEntityDict = new() {
{"male", "3942081117 3942081117"},
{"boy", "324034399 616600735"},
{"female", "3942081117 3942081117"},
{"girl", "616600735 616600735"},
};

Check notice on line 153 in ImperatorToCK3/Outputter/BookmarkOutputter.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/Outputter/BookmarkOutputter.cs#L72-L153

Complex Method
private static async Task OutputBookmarkGroup(Configuration config) {
var path = Path.Combine("output", config.OutputModName, "common/bookmarks/groups/00_bookmark_groups.txt");
await using var output = FileHelper.OpenWriteWithRetries(path, Encoding.UTF8);
Expand Down
8 changes: 8 additions & 0 deletions ImperatorToCK3/Outputter/FileTweaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public static async Task ModifyAndRemovePartsOfFiles(ModFilesystem ck3ModFS, str
ReadPartsOfFileToReplace(partsToModifyPerFile, "configurables/replaceable_file_blocks_roa.txt", warnIfNotFound: true);
}

if (config.AsiaExpansionProjectEnabled) {
Logger.Info("Reading unneeded parts of Rajas of Asia files...");
ReadPartsOfFileToRemove(partsToModifyPerFile, "configurables/removable_file_blocks_aep.txt", warnIfNotFound: true);

Logger.Info("Reading parts of Asia Expansion Project files to modify...");
ReadPartsOfFileToReplace(partsToModifyPerFile, "configurables/replaceable_file_blocks_aep.txt", warnIfNotFound: true);
}

if (config.WhenTheWorldStoppedMakingSenseEnabled) {
Logger.Info("Reading unneeded parts of When the World Stopped Making Sense files...");
ReadPartsOfFileToRemove(partsToModifyPerFile, "configurables/removable_file_blocks_wtwsms.txt", warnIfNotFound: true);
Expand Down
Loading