Skip to content

CK3 1.16 - Khans of the Steppe - Compatch #2589

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 7, 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
23 changes: 23 additions & 0 deletions ImperatorToCK3.UnitTests/Mappers/TagTitle/RankMappingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using commonItems;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.Imperator.Countries;
using ImperatorToCK3.Mappers.TagTitle;
using Xunit;

namespace ImperatorToCK3.UnitTests.Mappers.TagTitle;

[Collection("Sequential")]
public class RankMappingTests {
[Fact]
public void MappingCanContainIRGovernmentType() {
var reader = new BufferedReader("ir=local_power ir_government_type=tribal ck3=d");
var rankMapping = new RankMapping(reader);

// Should not match for a monarchy or a republic.
Assert.Null(rankMapping.Match("local_power", territoriesCount: 0, GovernmentType.monarchy));
Assert.Null(rankMapping.Match("local_power", territoriesCount: 0, GovernmentType.republic));

// Should match for a tribal country.
Assert.Equal(TitleRank.duchy, rankMapping.Match("local_power", territoriesCount: 0, GovernmentType.tribal));
}
}
2 changes: 2 additions & 0 deletions ImperatorToCK3/CK3/Titles/Title.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,127 +191,127 @@
vassal.DeJureLiege = this;
}
}
internal void InitializeFromTag(
Country country,
Dependency? dependency,
CountryCollection imperatorCountries,
LocDB irLocDB,
CK3LocDB ck3LocDB,
ProvinceMapper provinceMapper,
CoaMapper coaMapper,
GovernmentMapper governmentMapper,
SuccessionLawMapper successionLawMapper,
DefiniteFormMapper definiteFormMapper,
ReligionMapper religionMapper,
CultureMapper cultureMapper,
NicknameMapper nicknameMapper,
CharacterCollection characters,
Date conversionDate,
Configuration config,
IReadOnlyCollection<string> enabledCK3Dlcs
) {
ImperatorCountry = country;
ImperatorCountry.CK3Title = this;

LocBlock? validatedName = GetValidatedName(country, imperatorCountries, irLocDB);

HasDefiniteForm = definiteFormMapper.IsDefiniteForm(ImperatorCountry.Name);
RulerUsesTitleName = false;

PlayerCountry = ImperatorCountry.PlayerCountry;

ClearHolderSpecificHistory();

FillHolderAndGovernmentHistory(country, characters, governmentMapper, irLocDB, ck3LocDB, religionMapper, cultureMapper, nicknameMapper, provinceMapper, config, conversionDate, enabledCK3Dlcs);

// Determine color.
var color1Opt = ImperatorCountry.Color1;
if (color1Opt is not null) {
Color1 = color1Opt;
}

// determine successions laws
History.AddFieldValue(conversionDate,
"succession_laws",
"succession_laws",
successionLawMapper.GetCK3LawsForImperatorLaws(ImperatorCountry.GetLaws(), country.Government, enabledCK3Dlcs)
);

// Determine CoA.
if (IsCreatedFromImperator || !config.UseCK3Flags) {
bool warnIfMissing = !config.SkipDynamicCoAExtraction;
CoA = coaMapper.GetCoaForFlagName(ImperatorCountry.Flag, warnIfMissing);
}

// Determine other attributes:
// Set capital to Imperator tag's capital.
if (ImperatorCountry.CapitalProvinceId is not null) {
var srcCapital = ImperatorCountry.CapitalProvinceId.Value;
foreach (var ck3ProvId in provinceMapper.GetCK3ProvinceNumbers(srcCapital)) {
var foundCounty = parentCollection.GetCountyForProvince(ck3ProvId);
if (foundCounty is null) {
continue;
}

// If the title is a de jure duchy, potential capital must be within it.
if (Rank == TitleRank.duchy && DeJureVassals.Count > 0 && foundCounty.DeJureLiege?.Id != Id) {
continue;
}

CapitalCounty = foundCounty;
break;
}
}

// determine country name localization
var nameSet = false;
if (validatedName is not null) {
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(validatedName);
nameSet = true;
}
if (!nameSet) {
var irTagLoc = irLocDB.GetLocBlockForKey(ImperatorCountry.Tag);
if (irTagLoc is not null) {
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(irTagLoc);
nameSet = true;
}
}
if (!nameSet) {
// use unlocalized name if not empty
var name = ImperatorCountry.Name;
if (!string.IsNullOrEmpty(name)) {
Logger.Warn($"Using unlocalized Imperator name {name} as name for {Id}!");
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock[ConverterGlobals.PrimaryLanguage] = name;
nameSet = true;
}
}
// giving up
if (!nameSet) {
Logger.Warn($"{Id} needs help with localization! {ImperatorCountry.Name}?");
}

// determine adjective localization
TrySetAdjectiveLoc(irLocDB, imperatorCountries, ck3LocDB);

// If country is a subject, convert it to a vassal.
if (dependency is not null) {
var overLordTitle = imperatorCountries[dependency.OverlordId].CK3Title;
if (overLordTitle is null) {
Logger.Warn($"Can't find CK3 title for country {dependency.OverlordId}, overlord of {country.Id}.");
}
if (!config.StaticDeJure) {
DeJureLiege = overLordTitle;
}
SetDeFactoLiege(overLordTitle, dependency.StartDate);
}
}

/// <summary>
/// Fills title's history with Imperator and pre-Imperator rulers and sets appropriate government.
/// </summary>

Check notice on line 314 in ImperatorToCK3/CK3/Titles/Title.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Titles/Title.cs#L194-L314

Complex Method
private void FillHolderAndGovernmentHistory(Country imperatorCountry,
CharacterCollection characters,
GovernmentMapper governmentMapper,
Expand Down Expand Up @@ -399,84 +399,84 @@
liegeField.RemoveAllEntries(v => v is string str && str == liegeName);
}

private static LocBlock? GetValidatedName(Country imperatorCountry, CountryCollection imperatorCountries, LocDB irLocDB) {
switch (imperatorCountry.Name) {
// Hard code for Antigonid Kingdom, Seleucid Empire and Maurya.
// These countries use customizable localization for name and adjective.
case "PRY_DYN" when imperatorCountry.Monarch?.Family?.Key == "Antigonid":
const string pryLocKey = "get_pry_name_fetch";
var pryLocBlock = irLocDB.GetLocBlockForKey(pryLocKey);
if (pryLocBlock is null) {
return pryLocBlock;
}

var modifiedPrylocBlockToReturn = new LocBlock(pryLocKey, pryLocBlock);
const string pryNameKey = "PRY";
modifiedPrylocBlockToReturn.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(pryNameKey) ?? new LocBlock(pryNameKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Antigonid Kingdom"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${pryNameKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_pry_name_fallback")?[language];
}
return locToReturn;
});
return modifiedPrylocBlockToReturn;
case "PRY_DYN":
return irLocDB.GetLocBlockForKey("get_pry_name_fallback");
case "SEL_DYN" when imperatorCountry.Monarch?.Family?.Key == "Seleukid":
const string selLocKey = "get_sel_name_fetch";
var selLocBlock = irLocDB.GetLocBlockForKey(selLocKey);
if (selLocBlock is null) {
return selLocBlock;
}

var modifiedSelLocBlockToReturn = new LocBlock(selLocKey, selLocBlock);
const string selNameKey = "SEL";
modifiedSelLocBlockToReturn.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(selNameKey) ?? new LocBlock(selNameKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Seleukid Empire"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${selNameKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_sel_name_fallback")?[language];
}
return locToReturn;
});
return modifiedSelLocBlockToReturn;
case "SEL_DYN":
return irLocDB.GetLocBlockForKey("get_sel_name_fallback");
case "MRY_DYN" when imperatorCountry.Monarch?.Family?.Key == "Maurya":
const string mryLocKey = "get_mry_name_fetch";
var mryLocBlock = irLocDB.GetLocBlockForKey(mryLocKey);
if (mryLocBlock is null) {
return mryLocBlock;
}

var modifiedMryLocBlockToReturn = new LocBlock(mryLocKey, mryLocBlock);
const string mryNameKey = "MRY";
modifiedMryLocBlockToReturn.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(mryNameKey) ?? new LocBlock(mryNameKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Maurya"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${mryNameKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_mry_name_fallback")?[language];
}
return locToReturn;
});
return modifiedMryLocBlockToReturn;
case "MRY_DYN":
return irLocDB.GetLocBlockForKey("get_mry_name_fallback");
default:
return imperatorCountry.CountryName.GetNameLocBlock(irLocDB, imperatorCountries);
}
}

Check notice on line 479 in ImperatorToCK3/CK3/Titles/Title.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Titles/Title.cs#L402-L479

Complex Method
public static string DetermineId(
Country irCountry,
Dependency? dependency,
Expand Down Expand Up @@ -670,96 +670,96 @@
}
}

private void TrySetNameFromGovernorship(
Governorship governorship,
ImperatorRegionMapper irRegionMapper,
Country country,
Imperator.Provinces.ProvinceCollection irProvinces,
bool regionHasMultipleGovernorships,
LocDB irLocDB,
CK3LocDB ck3LocDB
) {
if (ck3LocDB.ContainsKey(Id)) {
return;
}

var nameSet = false;
var regionId = governorship.Region.Id;
irRegionMapper.Regions.TryGetValue(regionId, out var region);
LocBlock? regionLocBlock = irLocDB.GetLocBlockForKey(regionId);

// If any area in the region is at least 60% owned, use the area name for governorship name.
if (regionHasMultipleGovernorships && region is not null) {
Area? potentialSourceArea = null;
float biggestOwnershipPercentage = 0f;
foreach (var area in region.Areas) {
var areaProvinces = area.Provinces;
if (areaProvinces.Count == 0) {
continue;
}
var controlledProvinces = areaProvinces.Where(p => country.Equals(p.OwnerCountry));
var ownershipPercentage = (float)controlledProvinces.Count() / areaProvinces.Count;
if (ownershipPercentage < 0.6) {
continue;
}
if (ownershipPercentage > biggestOwnershipPercentage) {
potentialSourceArea = area;
biggestOwnershipPercentage = ownershipPercentage;
}
}

if (potentialSourceArea is not null && irLocDB.TryGetValue(potentialSourceArea.Id, out var areaLocBlock)) {
Logger.Debug($"Naming {Id} after I:R area {potentialSourceArea.Id} majorly ({biggestOwnershipPercentage:P}) controlled by {country.Tag}...");
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(areaLocBlock);
nameSet = true;

var adjLocBlock = ck3LocDB.GetOrCreateLocBlock($"{Id}_adj");
adjLocBlock.CopyFrom(nameLocBlock);
adjLocBlock.ModifyForEveryLanguage((loc, language) => language == "english" ? loc?.GetAdjective() : loc);
}
}
// Try to use the name of most developed owned territory in the region.
if (!nameSet && regionHasMultipleGovernorships && region is not null) {
var sourceProvince = irProvinces
.Where(p => region.ContainsProvince(p.Id) && country.Equals(p.OwnerCountry))
.MaxBy(p => p.CivilizationValue);
if (sourceProvince is not null && irLocDB.TryGetValue(sourceProvince.Name, out var provinceLocBlock)) {
Logger.Debug($"Naming {Id} after most developed I:R territory: {sourceProvince.Id}...");
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(provinceLocBlock);
nameSet = true;

var adjLocBlock = ck3LocDB.GetOrCreateLocBlock($"{Id}_adj");
adjLocBlock.CopyFrom(nameLocBlock);
adjLocBlock.ModifyForEveryLanguage((loc, language) => language == "english" ? loc?.GetAdjective() : loc);
}
}
// Try to use "<country adjective> <region name>" as governorship name if region has multiple governorships.
// Example: Mauretania -> Roman Mauretania
if (!nameSet && regionHasMultipleGovernorships && regionLocBlock is not null) {
var ck3Country = country.CK3Title;
if (ck3Country is not null && ck3LocDB.TryGetValue($"{ck3Country.Id}_adj", out var countryAdjectiveLocBlock)) {
Logger.Debug($"Naming {Id} after governorship with country adjective: {country.Tag} {governorship.Region.Id}...");
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(regionLocBlock);
nameLocBlock.ModifyForEveryLanguage(countryAdjectiveLocBlock,
(orig, adj, _) => $"{adj} {orig}"
);
nameSet = true;
}
}
if (!nameSet && regionLocBlock is not null) {
Logger.Debug($"Naming {Id} after governorship: {governorship.Region.Id}...");
var nameLocBlock = ck3LocDB.GetOrCreateLocBlock(Id);
nameLocBlock.CopyFrom(regionLocBlock);
nameSet = true;
}
if (!nameSet && Id.Contains("_IRTOCK3_")) {
Logger.Warn($"{Id} needs help with localization!");
}
}

Check notice on line 762 in ImperatorToCK3/CK3/Titles/Title.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Titles/Title.cs#L673-L762

Complex Method
public void LoadTitles(BufferedReader reader) {
var parser = new Parser();
RegisterKeys(parser);
Expand Down Expand Up @@ -817,155 +817,155 @@
History.AddFieldValue(date, "development_level", "change_development_level", value);
}

private void TrySetAdjectiveLoc(LocDB irLocDB, CountryCollection imperatorCountries, CK3LocDB ck3LocDB) {
if (ImperatorCountry is null) {
Logger.Warn($"Cannot set adjective for CK3 title {Id} from null Imperator country!");
return;
}

var adjSet = false;
var locKey = $"{Id}_adj";

if (ImperatorCountry.Tag is "PRY" or "SEL" or "MRY") {
// these tags use customizable loc for adj
LocBlock? validatedAdj;
switch (ImperatorCountry.Name) {
case "PRY_DYN" when ImperatorCountry.Monarch?.Family?.Key == "Antigonid":
var pryAdjLocBlock = irLocDB.GetLocBlockForKey("get_pry_adj_fetch");
if (pryAdjLocBlock is not null) {
const string pryAdjKey = "PRY_ADJ";
validatedAdj = new LocBlock(pryAdjLocBlock.Id, pryAdjLocBlock);
validatedAdj.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(pryAdjKey) ?? new LocBlock(pryAdjKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Antigonid"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${pryAdjKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_pry_adj_fallback")?[language];
}
return locToReturn;
});
} else {
validatedAdj = pryAdjLocBlock;
}

break;
case "PRY_DYN":
validatedAdj = irLocDB.GetLocBlockForKey("get_pry_adj_fallback");
break;
case "SEL_DYN" when ImperatorCountry.Monarch?.Family?.Key == "Seleukid":
var selAdjLocBlock = irLocDB.GetLocBlockForKey("get_sel_adj_fetch");
if (selAdjLocBlock is not null) {
const string selAdjKey = "SEL_ADJ";
validatedAdj = new LocBlock(selAdjLocBlock.Id, selAdjLocBlock);
validatedAdj.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(selAdjKey) ?? new LocBlock(selAdjKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Seleukid"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${selAdjKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_sel_adj_fallback")?[language];
}
return locToReturn;
});
} else {
validatedAdj = selAdjLocBlock;
}

break;
case "SEL_DYN":
validatedAdj = irLocDB.GetLocBlockForKey("get_sel_adj_fallback");
break;
case "MRY_DYN" when ImperatorCountry.Monarch?.Family?.Key == "Maurya":
var mryAdjLocBlock = irLocDB.GetLocBlockForKey("get_mry_adj_fetch");
if (mryAdjLocBlock is not null) {
const string mryAdjKey = "MRY_ADJ";
validatedAdj = new LocBlock(mryAdjLocBlock.Id, mryAdjLocBlock);
validatedAdj.ModifyForEveryLanguage(
otherBlock: irLocDB.GetLocBlockForKey(mryAdjKey) ?? new LocBlock(mryAdjKey, ConverterGlobals.PrimaryLanguage) {
[ConverterGlobals.PrimaryLanguage] = "Mauryan"
},
(loc, modifyingLoc, language) => {
var locToReturn = loc?.Replace($"${mryAdjKey}$", modifyingLoc);
if (locToReturn is not null && locToReturn.Contains("[GetCountry(")) {
locToReturn = irLocDB.GetLocBlockForKey("get_mry_adj_fallback")?[language];
}
return locToReturn;
});
} else {
validatedAdj = mryAdjLocBlock;
}
break;
case "MRY_DYN":
validatedAdj = irLocDB.GetLocBlockForKey("get_mry_adj_fallback");
break;
default:
validatedAdj = null;
break;
}

if (validatedAdj is not null) {
var adjLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
adjLocBlock.CopyFrom(validatedAdj);
adjSet = true;
}
}
if (!adjSet) {
var adjOpt = ImperatorCountry.CountryName.GetAdjectiveLocBlock(irLocDB, imperatorCountries);
if (adjOpt is not null) {
var adjLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
adjLocBlock.CopyFrom(adjOpt);
adjSet = true;
}
}
if (!adjSet) {
// Try to use the country name as adjective.
var adjLocalizationMatch = irLocDB.GetLocBlockForKey(ImperatorCountry.Tag);
if (adjLocalizationMatch is not null) {
var adjLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
adjLocBlock.CopyFrom(adjLocalizationMatch);
adjSet = true;
}
}

// Try to generate English adjective from country name.
if (!adjSet) {
if (ck3LocDB.TryGetValue(Id, out var nameLocBlock) && nameLocBlock["english"] is {} name) {
// If name has 3 characters and last 2 characters are digits, it's probably a raw Imperator tag.
// In that case, we don't want to use it as a base for adjective.
if (!(name.Length == 3 && char.IsDigit(name[1]) && char.IsDigit(name[2]))) {
var generatedAdjective = name.GetAdjective();
Logger.Debug($"Generated adjective for country \"{name}\": \"{generatedAdjective}\"");

var adjLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
adjLocBlock["english"] = generatedAdjective;
adjSet = true;
}
}
}

if (!adjSet) {
// Use unlocalized name if not empty
var name = ImperatorCountry.Name;
if (!string.IsNullOrEmpty(name)) {
Logger.Warn($"Using unlocalized Imperator name {name} as adjective for {Id}!");
var adjLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
adjLocBlock[ConverterGlobals.PrimaryLanguage] = name;
adjSet = true;
}
}

// Give up.
if (!adjSet) {
Logger.Warn($"{Id} needs help with localization for adjective! {ImperatorCountry.Name}_adj?");
}
}
[commonItems.Serialization.NonSerialized] public string? CoA { get; private set; }

[SerializedName("capital")] public string? CapitalCountyId { get; private set; }
[commonItems.Serialization.NonSerialized]

Check notice on line 968 in ImperatorToCK3/CK3/Titles/Title.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Titles/Title.cs#L820-L968

Complex Method
public Title? CapitalCounty {
get {
if (CapitalCountyId is null) {
Expand Down Expand Up @@ -1106,6 +1106,7 @@
[SerializedName("always_follows_primary_heir")] public bool? AlwaysFollowsPrimaryHeir { get; set; }
[SerializedName("de_jure_drift_disabled")] public bool? DeJureDriftDisabled { get; set; }
[SerializedName("can_be_named_after_dynasty")] public bool? CanBeNamedAfterDynasty { get; set; }
[SerializedName("can_use_nomadic_naming")] public bool? CanUseNomadicNaming { get; set; }
[SerializedName("male_names")] public List<string>? MaleNames { get; private set; }
// <culture, loc key>
[SerializedName("cultural_names")] public Dictionary<string, string>? CulturalNames { get; private set; }
Expand Down Expand Up @@ -1197,6 +1198,7 @@
parser.RegisterKeyword("always_follows_primary_heir", reader => AlwaysFollowsPrimaryHeir = reader.GetBool());
parser.RegisterKeyword("de_jure_drift_disabled", reader => DeJureDriftDisabled = reader.GetBool());
parser.RegisterKeyword("can_be_named_after_dynasty", reader => CanBeNamedAfterDynasty = reader.GetBool());
parser.RegisterKeyword("can_use_nomadic_naming", reader => CanUseNomadicNaming = reader.GetBool());
parser.RegisterKeyword("male_names", reader => MaleNames = reader.GetStrings());
parser.RegisterKeyword("cultural_names", reader => CulturalNames = reader.GetAssignmentsAsDict());

Expand Down Expand Up @@ -1436,113 +1438,113 @@
}
}

private void AppointCouncilMembersFromImperator(ReligionCollection religionCollection,
Dictionary<string, string[]> councilPositionToSourcesDict,
List<OfficeJob> convertibleJobs,
HashSet<string> alreadyEmployedCharacters,
Character ck3Ruler,
Date irSaveDate) {
Dictionary<string, int> heldTitlesPerCharacterCache = [];

foreach (var (ck3Position, sources) in councilPositionToSourcesDict) {
// The order of I:R source position types is important - the first filled one found will be used.
foreach (var sourceOfficeType in sources) {
var job = convertibleJobs.Find(o => o.OfficeType == sourceOfficeType);
if (job is null) {
continue;
}

var ck3Official = job.Character.CK3Character;
if (ck3Official is null) {
continue;
}
if (alreadyEmployedCharacters.Contains(ck3Official.Id)) {
continue;
}

// A ruler cannot be their own councillor.
if (ck3Official.Id == ck3Ruler.Id) {
continue;
}

if (!heldTitlesPerCharacterCache.TryGetValue(ck3Official.Id, out int heldTitlesCount)) {
heldTitlesCount = parentCollection.Count(t => t.GetHolderId(irSaveDate) == ck3Official.Id);
heldTitlesPerCharacterCache[ck3Official.Id] = heldTitlesCount;
}

if (ck3Position == "councillor_court_chaplain") {
// Court chaplains need to have the same faith as the ruler.
var rulerFaithId = ck3Ruler.GetFaithId(irSaveDate);
if (rulerFaithId is null || rulerFaithId != ck3Official.GetFaithId(irSaveDate)) {
continue;
}

// If the faith has Disallowed Clerical Marriage, don't allow married court chaplains.
var rulerFaith = religionCollection.GetFaith(rulerFaithId);
if (rulerFaith is null) {
continue;
}
if (rulerFaith.HasDoctrine("doctrine_clerical_marriage_disallowed")) {
if (ck3Official.GetSpouseIds(irSaveDate).Count > 0) {
continue;
}
}

// If the court faith has doctrine_theocracy_temporal (Theocratic Clerical Tradition), the court chaplain should
// be either theocratic or landless.
// For the purpose of the conversion, we simply require them to be landless.
if (rulerFaith.HasDoctrine("doctrine_theocracy_temporal")) {
if (heldTitlesCount > 0) {
continue;
}
}

// Skip if the faith doesn't allow the character's gender to be clergy.
var clericalGenderDoctrines = rulerFaith.GetDoctrineIdsForDoctrineCategoryId("doctrine_clerical_gender");
if (clericalGenderDoctrines.Count != 0) {
if (clericalGenderDoctrines.Contains("doctrine_clerical_gender_female_only") && !ck3Official.Female) {
continue;
}
if (clericalGenderDoctrines.Contains("doctrine_clerical_gender_male_only") && ck3Official.Female) {
continue;
}
}
} else if (ck3Position == "councillor_steward" || ck3Position == "councillor_chancellor" || ck3Position == "councillor_marshal") {
// Unless they are rulers, stewards, chancellors and marshals need to have the dominant gender of the faith.
if (heldTitlesCount == 0) {
var courtFaith = ck3Ruler.GetFaithId(irSaveDate);
if (courtFaith is not null) {
var dominantGenderDoctrines = religionCollection.GetFaith(courtFaith)?
.GetDoctrineIdsForDoctrineCategoryId("doctrine_gender");
if (dominantGenderDoctrines is null) {
continue;
}
if (dominantGenderDoctrines.Contains("doctrine_gender_male_dominated") && ck3Official.Female) {
continue;
}
if (dominantGenderDoctrines.Contains("doctrine_gender_female_dominated") && !ck3Official.Female) {
continue;
}
}
}
}

// We only need to set the employer when the council member is landless.
if (heldTitlesCount == 0) {
ck3Official.History.AddFieldValue(irSaveDate, "employer", "employer", ck3Ruler.Id);
}
ck3Official.History.AddFieldValue(irSaveDate, "council_position", "give_council_position", ck3Position);

// One character should only hold one CK3 position.
convertibleJobs.Remove(job);
alreadyEmployedCharacters.Add(ck3Official.Id);

break;
}
}
}

// used by county titles only

Check notice on line 1547 in ImperatorToCK3/CK3/Titles/Title.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Titles/Title.cs#L1441-L1547

Complex Method
[commonItems.Serialization.NonSerialized] public IEnumerable<ulong> CountyProvinceIds => DeJureVassals
.Where(v => v.Rank == TitleRank.barony && v.ProvinceId.HasValue)
.Select(v => v.ProvinceId!.Value);
Expand Down
2 changes: 2 additions & 0 deletions ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,113 +697,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 806 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L700-L806

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 @@ -957,159 +957,160 @@
}
}

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 notice on line 1113 in ImperatorToCK3/CK3/World.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L960-L1113

Complex Method

private void DetermineCK3Dlcs(Configuration config) {
var dlcFolderPath = Path.Join(config.CK3Path, "game/dlc");
Expand Down Expand Up @@ -1138,6 +1139,7 @@
{"dlc017.dlc", "medieval_monuments"},
{"dlc018.dlc", "arctic_attire"},
{"dlc019.dlc", "crowns_of_the_world"},
{"dlc020.dlc", "khans_of_the_steppe"},
};

var dlcFiles = Directory.GetFiles(dlcFolderPath, "*.dlc", SearchOption.AllDirectories);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ tradition_ekvopetobos = {
}
}

if = {
limit = {
culture_tradition_reduction_trigger = { TRADITION = tradition_ekvopetobos }
}
multiply = {
value = 0.5
desc = inspired_by_culture_desc
}
}

multiply = tradition_replacement_cost_if_relevant
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Last updated: CK3 patch 1.14.0.1
# Last updated: CK3 patch 1.16.0
MountedWarriorTerm = {
type = character

Expand Down Expand Up @@ -130,6 +130,20 @@ FortifiedBuilding = { #Castle, Fort, Kasbah etc.
ResidenceBuilding = { #Castle, Palace, etc.
type = character

text = {
trigger = {
government_has_flag = government_is_nomadic
}
localization_key = yurt_residence
}

text = {
trigger = {
government_has_flag = government_is_tribal
}
localization_key = hall_residence
}

text = {
trigger = {
government_has_flag = government_is_landless_adventurer
Expand Down Expand Up @@ -185,6 +199,20 @@ ResidenceBuilding = { #Castle, Palace, etc.
ResidenceBuildingPlural = { #Castles, Palaces, etc.
type = character

text = {
trigger = {
government_has_flag = government_is_nomadic
}
localization_key = yurt_residence_plural
}

text = {
trigger = {
government_has_flag = government_is_tribal
}
localization_key = hall_residence_plural
}

text = {
trigger = {
government_has_flag = government_is_landless_adventurer
Expand Down Expand Up @@ -3530,3 +3558,165 @@ GetKnightErrantAnimal = {
}
}

RegionalWeaponMetal = {
type = character

text = {
trigger = {
location = {
OR = {
geographical_region = world_india_deccan
geographical_region = world_india_bengal
geographical_region = world_india_rajastan
geographical_region = world_burma
}
}
}
localization_key = metal_wootz_steel
}
text = {
trigger = {
location = {
OR = {
geographical_region = world_middle_east
geographical_region = world_asia_minor
geographical_region = world_africa_north_east
}
}
}
localization_key = metal_damascus_steel
}
text = {
trigger = {
location = {
OR = {
geographical_region = world_europe_east
geographical_region = world_steppe_tarim
geographical_region = world_steppe_west
geographical_region = world_steppe_central
geographical_region = world_steppe_east
geographical_region = world_siberia
}
}
}
localization_key = metal_bulat_steel
}
text = {
trigger = {
NOR = {
government_has_flag = government_is_tribal
government_has_flag = government_is_nomadic
}
}
localization_key = metal_steel
}

text = {
localization_key = metal_iron
fallback = yes
}
}

RegionalFabric = {
type = character
random_valid = yes

text = {
trigger = {
location = {
OR = {
geographical_region = world_europe_west
geographical_region = world_europe_north
geographical_region = world_europe_south
geographical_region = world_europe_east
}
}
}
localization_key = cloth_linen
}
text = {
trigger = {
location = {
OR = {
geographical_region = world_africa_north_east
geographical_region = world_africa_east
}
}
}
localization_key = cloth_cotton
}
text = {
trigger = {
location = {
NOR = {
geographical_region = world_africa_west
geographical_region = world_africa_east
}
}
}
localization_key = cloth_silk
}
text = {
trigger = {
location = {
NOR = {
geographical_region = world_africa_west
geographical_region = world_africa_east
}
}
}
localization_key = cloth_satin
}
text = {
trigger = {
location = {
NOR = {
geographical_region = world_africa_west
geographical_region = world_africa_east
}
}
}
localization_key = cloth_velvet
}
text = {
trigger = {
location = {
NOR = {
geographical_region = world_africa_west
geographical_region = world_africa_east
}
}
}
localization_key = cloth_samite
}
text = {
trigger = {
location = {
OR = {
geographical_region = world_europe_west
geographical_region = world_europe_north
geographical_region = world_europe_south
geographical_region = world_europe_east
}
}
NOT = {
religion = religion:judaism_religion
}
}
localization_key = cloth_fustian
}
text = {
trigger = {
location = {
geographical_region = world_europe_west_britannia
}
current_date >= 1200.1.1
}
localization_key = cloth_worsted_wool
}

text = {
localization_key = cloth_wool
fallback = yes
}
}
Loading
Loading