Skip to content
Draft
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
175 changes: 171 additions & 4 deletions .github/workflows/ck3-tiger.conf

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions ImperatorToCK3.UnitTests/CK3/Characters/CK3CharacterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,27 @@
Assert.Equal("Predrag \"Peja\" Stojaković", ck3LocDB.GetLocBlockForKey(irCharacter2NameLocKey)!["english"]);
Assert.Equal($" {irCharacter2NameLocKey}: \"Predrag \\\"Peja\\\" Stojaković\"", ck3LocDB.GetYmlLocLineForLanguage(irCharacter2NameLocKey, "english"));
}

[Fact]
public void VariablesAreConvertedFromImperator() {
var imperatorCharacter = new ImperatorToCK3.Imperator.Characters.Character.Parse(

Check failure on line 421 in ImperatorToCK3.UnitTests/CK3/Characters/CK3CharacterTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

The type name 'Parse' does not exist in the type 'Character'

Check failure on line 421 in ImperatorToCK3.UnitTests/CK3/Characters/CK3CharacterTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

The type name 'Parse' does not exist in the type 'Character'
new BufferedReader(
"variables = {\n" +
" irtock3_ambition_progress = 1234000\n" + // 12.34 * 100000
" irtock3_some_other_variable = 18446744073708317616\n" + // 2^64 - 12.34 * 100000
"}"
),
"1",
new GenesDB()
);

var character = builder
.WithImperatorCharacter(imperatorCharacter)
.Build();
var date = new Date(1, 1, 1);
Assert.Equal(12.34, character.History.GetFieldValue("irtock3_ambition_progress", date));
Assert.Equal(-12.34, character.History.GetFieldValue("irtock3_some_other_variable", date));
}

[Fact]
public void AgeSexReturnsCorrectString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ public void MarriageDateCanBeEstimatedFromChild() {
new NicknameMapper(),
new ProvinceMapper(),
new DeathReasonMapper(),
new DNAFactory(irModFS, ck3ModFS),
new TestCK3LocDB(),
endDate,
configuration);
Expand Down Expand Up @@ -147,7 +146,6 @@ public void MarriageDateCanBeEstimatedFromUnbornChild() {
new NicknameMapper(),
new ProvinceMapper(),
new DeathReasonMapper(),
new DNAFactory(irModFS, ck3ModFS),
new TestCK3LocDB(),
endDate,
configuration);
Expand Down Expand Up @@ -199,7 +197,6 @@ public void OnlyEarlyPregnanciesAreImportedFromImperator() {
new NicknameMapper(),
new ProvinceMapper(),
new DeathReasonMapper(),
new DNAFactory(irModFS, ck3ModFS),
new TestCK3LocDB(),
conversionDate,
configuration);
Expand Down Expand Up @@ -344,7 +341,6 @@ public void ImperatorCountriesGoldCanBeDistributedAmongRulerAndVassals() {
nicknameMapper,
provinceMapper,
deathReasonMapper,
new DNAFactory(irModFS, ck3ModFS),
ck3LocDB,
conversionDate,
config);
Expand Down Expand Up @@ -434,7 +430,6 @@ public void ImperatorCharacterNamesCanBeOverriddenByConfigurable() {
new NicknameMapper(),
new ProvinceMapper(),
new DeathReasonMapper(),
new DNAFactory(irModFS, ck3ModFS),
new TestCK3LocDB(),
new Date(1000, 1, 1, AUC: true),
config);
Expand Down
63 changes: 62 additions & 1 deletion ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@

var ck3Provinces = new ProvinceCollection { new(1) };
var ck3RegionMapper = new CK3RegionMapper();
AreaCollection areas = new();
AreaCollection areas = [];
areas.LoadAreas(irModFS, irWorld.Provinces);
var irRegionMapper = new ImperatorRegionMapper(areas, new MapData(irModFS));
irRegionMapper.LoadRegions(irModFS, colorFactory);
Expand Down Expand Up @@ -145,4 +145,65 @@
Assert.NotNull(primarySourceProvince);
Assert.Equal((ulong)6, primarySourceProvince.Id); // province of country 2
}

[Fact]
public void VariablesAreConvertedFromImperator() {
//var country1 = new Country(1);

Check notice on line 151 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs#L151

Remove this commented out code.
var irProvsReader = new BufferedReader(
"""
= {
1={
{
flag="bool_negative
data={
type=boolean # false
}
}
{
flag="bool_positive"
data={
type=boolean
identity=1 # true
}
}
{
flag="number_positive"
data={
type=value
identity=1234000 # 12.34
}
}
{
flag="number_negative"
data={
type=value
identity=18446744073708317616 # -12.34
}
}
}
}
"""
);
var provinces = new ImperatorToCK3.Imperator.Provinces.ProvinceCollection();
provinces.LoadProvinces(irProvsReader, states: [], countries: [], new MapData(irModFS));
var conversionDate = new Date(476, 1, 1);
var config = new Configuration { CK3BookmarkDate = conversionDate };
var irWorld = new TestImperatorWorld(config);

var provinceMapper = new ProvinceMapper();
const string provinceMappingsPath = "TestFiles/LandedTitlesTests/province_mappings.txt";
provinceMapper.LoadMappings(provinceMappingsPath);

var ck3Provinces = new ProvinceCollection { new(1) };
var ck3MapData = new MapData(ck3ModFs);
ck3MapData.ProvinceDefinitions.Add(new ProvinceDefinition(1));
ck3Provinces.ImportImperatorProvinces(irWorld, ck3MapData, titles: [], new CultureMapper(new ImperatorRegionMapper(new AreaCollection(), new MapData(irModFS)), new CK3RegionMapper(), new CultureCollection(colorFactory, new PillarCollection(colorFactory, []), [])), new ReligionMapper(new ReligionCollection(new Title.LandedTitles()), new ImperatorRegionMapper(new AreaCollection(), new MapData(irModFS)), new CK3RegionMapper()), provinceMapper, new Date(867, 1, 1), new Configuration());

var ck3Province = ck3Provinces[1];
Assert.Equal(4, ck3Province.Variables.Count);

Check failure on line 203 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 203 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)
Assert.Equal(false, ck3Province.Variables.GetValueById("bool_negative"));

Check failure on line 204 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 204 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)
Assert.Equal(true, ck3Province.Variables.GetValueById("bool_positive"));

Check failure on line 205 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 205 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)
Assert.Equal(12.34, ck3Province.Variables.GetValueById("number_positive"));

Check failure on line 206 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 206 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)
Assert.Equal(-12.34, ck3Province.Variables.GetValueById("number_negative"));

Check failure on line 207 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 207 in ImperatorToCK3.UnitTests/CK3/Provinces/ProvincesTests.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

'Province' does not contain a definition for 'Variables' and no accessible extension method 'Variables' accepting a first argument of type 'Province' could be found (are you missing a using directive or an assembly reference?)
}
}
1 change: 0 additions & 1 deletion ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ public void GovernorshipsCanBeRecognizedAsCountyLevel() {
nicknameMapper,
provinceMapper,
deathReasonMapper,
dnaFactory,
new TestCK3LocDB(),
conversionDate,
config
Expand Down
1 change: 0 additions & 1 deletion ImperatorToCK3/CK3/Characters/CharacterCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
NicknameMapper nicknameMapper,
ProvinceMapper provinceMapper,
DeathReasonMapper deathReasonMapper,
DNAFactory dnaFactory,
CK3LocDB ck3LocDB,
Date conversionDate,
Configuration config
Expand Down Expand Up @@ -283,38 +282,38 @@
return conversionDate;
}

private static Date? GetBirthDateOfFirstCommonChild(Imperator.Characters.Character father, Imperator.Characters.Character mother) {
Date? firstChildBirthDate = null;

if (father.Children.Count > 0 && mother.Children.Count > 0) {
var smallerCollection = father.Children.Count <= mother.Children.Count ? father.Children : mother.Children;
var largerCollection = father.Children.Count > mother.Children.Count ? father.Children : mother.Children;

foreach (var (childId, child) in smallerCollection) {
if (!largerCollection.ContainsKey(childId)) {
continue;
}
if (firstChildBirthDate is null || child.BirthDate < firstChildBirthDate) {
firstChildBirthDate = child.BirthDate;
}
}
if (firstChildBirthDate is not null) {
return firstChildBirthDate;
}
}

foreach (var unborn in mother.Unborns) {
if (unborn.FatherId != father.Id) {
continue;
}
if (firstChildBirthDate is null || unborn.BirthDate < firstChildBirthDate) {
firstChildBirthDate = unborn.BirthDate;
}
}

return firstChildBirthDate;
}

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/CharacterCollection.cs#L285-L316

Complex Method
private static Date? GetMarriageDeathDate(Imperator.Characters.Character husband, Imperator.Characters.Character wife) {
if (husband.DeathDate is not null && wife.DeathDate is not null) {
return husband.DeathDate < wife.DeathDate ? husband.DeathDate : wife.DeathDate;
Expand Down
3 changes: 0 additions & 3 deletions ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,326 +71,323 @@

/// <summary>
/// Date based on I:R save date, but normalized for CK3 purposes.
/// </summary>
public Date CorrectedDate { get; private set; } = new Date(2, 1, 1); // overwritten by DetermineCK3BookmarkDate

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

warMapper.DetectUnmappedWarGoals(impWorld.ModFS);

DetermineCK3Dlcs(config);
LoadAndDetectCK3Mods(config);

// Now that the CK3 mods are detected, we can build a collection of variables for Liquid files.
var liquidVariables = config.GetLiquidVariables();

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

DetermineCK3BookmarkDate(impWorld, config);

// 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, liquidVariables);

// 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();
}
);

var ck3ModFlags = config.GetCK3ModFlags();

Parallel.Invoke(
() => provinceMapper.DetectInvalidMappings(impWorld.MapData, MapData), // depends on ProvinceMapper and MapData
() => { // 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, liquidVariables);
Cultures = new CultureCollection(ck3ColorFactory, CulturalPillars, ck3ModFlags);
Cultures.LoadNameLists(ModFS);
Cultures.LoadInnovationIds(ModFS);
Cultures.LoadCultures(ModFS);
Cultures.LoadConverterCultures("configurables/converter_cultures.txt");
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, ck3ColorFactory);

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

Title.LandedTitles overrideTitles = [];
overrideTitles.LoadStaticTitles(ck3ColorFactory);
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.liquid", ck3ColorFactory, liquidVariables);
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);
traitMapper.LogUnmappedImperatorTraits(impWorld.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, liquidVariables),

() => { // 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);

GovernmentMapper governmentMapper = InitializeGovernmentMapper();

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

SuccessionLawMapper successionLawMapper = new("configurables/succession_law_map.liquid", liquidVariables);
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, MapData.ProvinceDefinitions, 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);
ImportImperatorHoldingsIfNotDisabledByConfiguration(impWorld, config);

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

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

// Apply religion-specific tweaks.
RemoveIslamFromMapIfNotInImperator(impWorld, config);
HandleChristianity(impWorld, config);
HandleManichaeism(impWorld, config);

// Now that Islam has been handled, we can generate filler holders without the risk of making them Muslim.
GenerateFillerHoldersForUnownedLands(impWorld.Provinces, Cultures, config);
// The filler holders have overwritten some counties, so now we can remove holders from titles that have become landless.
LandedTitles.RemoveInvalidLandlessTitles(config.CK3BookmarkDate);
Logger.IncrementProgress();
if (!config.StaticDeJure) {
LandedTitles.SetDeJureKingdomsAndAbove(config.CK3BookmarkDate, Cultures, Characters, MapData, CK3RegionMapper, 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 Imperator 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, impWorld.Countries, CorrectedDate, unitTypeMapper, MenAtArmsTypes, provinceMapper, LocDB, config);

// For titles linked to I:R countries with chinese_empire government, ensure the character variables
// needed for Dynastic Cycle script are calculated and stores as character variables.
Characters.CalculateChineseDynasticCycleVariables(LandedTitles, impWorld.EndDate, config.CK3BookmarkDate);

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

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L74-L390

Complex Method
Religions.DetermineHolySites(Provinces, impWorld.Religions, holySiteEffectMapper, config.CK3BookmarkDate);

Religions.GenerateMissingReligiousHeads(LandedTitles, Characters, Provinces, Cultures, config.CK3BookmarkDate);
Expand Down Expand Up @@ -603,93 +600,93 @@
}
}

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.Counties) {
if (county.NobleFamily == true) {
continue;
}
if (county.CapitalBaronyProvinceId is null) {
Logger.Warn($"County {county} has no capital barony province!");
continue;
}
ulong capitalBaronyProvId = (ulong)county.CapitalBaronyProvinceId;
if (capitalBaronyProvId == 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(capitalBaronyProvId)) {
Logger.Warn($"Capital barony province not found: {capitalBaronyProvId}");
continue;
}

var ck3CapitalBaronyProvince = Provinces[capitalBaronyProvId];
var irProvince = ck3CapitalBaronyProvince.PrimaryImperatorProvince;
// If the county capital's primary I:R province has no owner,
// try to use other source provinces.
// If this fails, try using source provinces of other baronies.
if (irProvince?.OwnerCountry is null) {
foreach (var secondarySourceProv in ck3CapitalBaronyProvince.SecondaryImperatorProvinces) {
if (secondarySourceProv.OwnerCountry is null) {
continue;
}

irProvince = secondarySourceProv;
Logger.Debug($"Using secondary source province {secondarySourceProv.Id} of capital barony" +
$"province {capitalBaronyProvId} for history of county {county.Id}!");
}
}
if (irProvince?.OwnerCountry is null) {
foreach (var barony in county.DeJureVassals) {
var baronyCk3ProvId = barony.ProvinceId;
if (baronyCk3ProvId is null) {
continue;
}
var primarySourceProvForBarony = Provinces[baronyCk3ProvId.Value].PrimaryImperatorProvince;
if (primarySourceProvForBarony?.OwnerCountry is null) {
continue;
}

irProvince = primarySourceProvForBarony;
Logger.Debug($"Using province {baronyCk3ProvId.Value} of barony {barony.Id} instead of" +
$"capital barony province {capitalBaronyProvId} for history of county {county.Id}!");
break;
}
}
if (irProvince?.OwnerCountry is null) {
foreach (var barony in county.DeJureVassals) {
var baronyCk3ProvId = barony.ProvinceId;
if (baronyCk3ProvId is null) {
continue;
}

var secondaryProvWithOwner = Provinces[baronyCk3ProvId.Value].SecondaryImperatorProvinces
.FirstOrDefault(p => p.OwnerCountry is not null);
if (secondaryProvWithOwner is null) {
continue;
}

irProvince = secondaryProvWithOwner;
Logger.Debug($"Using province {baronyCk3ProvId.Value} of barony {barony.Id} instead of" +
$"capital barony province {capitalBaronyProvId} for history of county {county.Id}!");
break;
}
}

if (irProvince is null) { // probably outside of Imperator map
continue;
}

OverwriteCountyHistory(county, irProvince, irCountries, countyLevelCountries, governorshipsSet, countyLevelGovernorshipsSet, impCharacters, irProvinces, conversionDate);
}
Logger.IncrementProgress();
}

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L603-L689

Complex Method
private void OverwriteCountyHistory(Title county, Imperator.Provinces.Province irProvince, CountryCollection irCountries,
List<KeyValuePair<Country, Dependency?>> countyLevelCountries, FrozenSet<Governorship> governorshipsSet, FrozenSet<Governorship> countyLevelGovernorshipsSet,
Imperator.Characters.CharacterCollection irCharacters, Imperator.Provinces.ProvinceCollection irProvinces, Date conversionDate) {
Expand Down Expand Up @@ -737,113 +734,113 @@
var countryCapitalDuchy = ck3CapitalCounty.DeJureLiege;
var deJureDuchyOfCounty = county.DeJureLiege;
return countryCapitalDuchy is not null && deJureDuchyOfCounty is not null && countryCapitalDuchy.Id == deJureDuchyOfCounty.Id;
}

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 parentRegionName = imperatorRegionMapper.GetParentRegionName(irProvince.Id);
var governorship = governorshipsSet
.FirstOrDefault(g => g.Country.Id == irCountry.Id && g.Region.Id == parentRegionName);
if (governorship is null) {
// We have no matching governorship.
return false;
}

if (ShouldSkipGovernorDueToCapitalDuchy(county, ck3Country)) {
return false;
}

// give county to governor
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;
}

private void GiveCountyToMonarch(Title county, Title ck3Country) {
var date = ck3Country.GetDateOfLastHolderChange();
var holderId = ck3Country.GetHolderId(date);

if (Characters.TryGetValue(holderId, out var holder)) {
county.ClearHolderSpecificHistory();
county.SetHolder(holder, date);
} else {
Logger.Warn($"Holder {holderId} of county {county} doesn't exist!");
}
county.SetDeFactoLiege(null, date);
}

private void GiveCountyToGovernor(Title county, string ck3GovernorshipId) {
var ck3Governorship = LandedTitles[ck3GovernorshipId];
var holderChangeDate = ck3Governorship.GetDateOfLastHolderChange();
var holderId = ck3Governorship.GetHolderId(holderChangeDate);
if (Characters.TryGetValue(holderId, out var governor)) {
county.ClearHolderSpecificHistory();
county.SetHolder(governor, holderChangeDate);
} else {
Logger.Warn($"Holder {holderId} of county {county} doesn't exist!");
}
county.SetDeFactoLiege(null, holderChangeDate);
}

private static void GiveCountyToCountyLevelGovernor(Title county,
Governorship governorship,
Title ck3Country,
Imperator.Characters.CharacterCollection impCharacters) {
var holderChangeDate = governorship.StartDate;
var impGovernor = impCharacters[governorship.CharacterId];
var governor = impGovernor.CK3Character;

county.ClearHolderSpecificHistory();
county.SetHolder(governor, holderChangeDate);
county.SetDeFactoLiege(ck3Country, holderChangeDate);
}

private bool TryGiveCountyToCountyLevelRuler(Title county,
Country irCountry,
List<KeyValuePair<Country, Dependency?>> countyLevelCountries,
CountryCollection irCountries) {
var matchingCountyLevelRuler = countyLevelCountries
.FirstOrDefault(c => c.Key.Id == irCountry.Id);
if (matchingCountyLevelRuler.Key is null) {
return false;
}
var dependency = matchingCountyLevelRuler.Value;

// Give county to ruler.
var ck3Ruler = irCountry.Monarch?.CK3Character;
county.ClearHolderSpecificHistory();
var ruleStartDate = irCountry.RulerTerms.OrderBy(t => t.StartDate).Last().StartDate;
county.SetHolder(ck3Ruler, ruleStartDate);
if (dependency is not null) {
var irOverlord = dependency.OverlordId;
var ck3Overlord = irCountries[irOverlord].CK3Title;
county.SetDeFactoLiege(ck3Overlord, dependency.StartDate);
} else {
county.SetDeFactoLiege(null, ruleStartDate);
}
RevokeBaroniesFromCountyGivenToImperatorCharacter(county);
return true;

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L737-L843

Complex Method
}

private static void RevokeBaroniesFromCountyGivenToImperatorCharacter(Title county) {
Expand Down Expand Up @@ -1186,178 +1183,178 @@
}
}

private void GenerateFillerHoldersForUnownedLands(Imperator.Provinces.ProvinceCollection irProvinces, 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) {
if (county.NobleFamily == true) {
continue;
}

// If the county's provinces are have no owners in Imperator,
// // generate a filler holder even if a valid vanilla holder exists.
// This fixes stuff like a vanilla Tang China in one county.
var irProvIds = county.CountyProvinceIds
.SelectMany(id => provinceMapper.GetImperatorProvinceNumbers(id)).ToArray();
if (irProvIds.Length > 0 && irProvIds.All(p => !irProvinces.TryGetValue(p, out var irProv) || irProv.OwnerCountry is null)) {
Logger.Debug($"Adding {county.Id} to unheld counties because all its provinces are mapped to I:R wastelands.");
unheldCounties.Add(county);
continue;
}

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);
duchy.SetDeFactoLiege(newLiege: null, date);
duchyIdToHolderDict[duchy.Id] = holder;
} else {
county.SetGovernment(government, date);
}
county.SetDeFactoLiege(newLiege: null, date);
}
}

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L1186-L1357

Very Complex Method
private void DetermineCK3Dlcs(Configuration config) {
var dlcFolderPath = Path.Join(config.CK3Path, "game/dlc");
if (!Directory.Exists(dlcFolderPath)) {
Expand Down
8 changes: 8 additions & 0 deletions ImperatorToCK3/Imperator/CharacterVariable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using commonItems.Collections;

namespace ImperatorToCK3.Imperator;

public readonly struct Variable(string id, object value) : IIdentifiable<string> {

Check warning on line 5 in ImperatorToCK3/Imperator/CharacterVariable.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

ImperatorToCK3/Imperator/CharacterVariable.cs#L5

Implement 'IEquatable<T>' in value type 'Variable'.
public string Id { get; } = id;
public object Value { get; init; } = value;
}
92 changes: 74 additions & 18 deletions ImperatorToCK3/Imperator/Characters/Character.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using commonItems;
using commonItems;
using commonItems.Collections;
using ImperatorToCK3.CK3.Characters;
using ImperatorToCK3.CommonUtils;
using ImperatorToCK3.Imperator.Countries;
using ImperatorToCK3.Imperator.Families;
using ImperatorToCK3.CommonUtils.Genes;
using ImperatorToCK3.CommonUtils.Map;
using Open.Collections;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

Expand Down Expand Up @@ -90,8 +92,8 @@

public List<string> Traits { get; set; } = [];
public CharacterAttributes Attributes { get; private set; } = new();
public IReadOnlySet<string> Variables { get; private set; } = ImmutableHashSet<string>.Empty;
public bool IsBald => Variables.Contains("bald");
public IdObjectCollection<string, Variable> Variables { get; private set; } = [];
public bool IsBald { get; private set; }
public uint Age { get; private set; } = 0;
public string? DNA { get; private set; }
public PortraitData? PortraitData { get; private set; }
Expand Down Expand Up @@ -145,23 +147,77 @@
}

private static void SetVariables(Character character, BufferedReader reader) {
var variables = new HashSet<string>();
var variablesParser = new Parser();
variablesParser.RegisterKeyword("data", dataReader => {
var blobParser = new Parser();
blobParser.RegisterKeyword("flag", blobReader => variables.Add(string.Intern(blobReader.GetString())));
blobParser.IgnoreUnregisteredItems();

foreach (var blob in new BlobList(dataReader).Blobs) {
blobParser.ParseStream(new BufferedReader(blob));
var variablesParser = new Parser();
variablesParser.RegisterKeyword("data", dataReader => {
foreach (var blob in new BlobList(dataReader).Blobs) {
ParseCharacterVariable(blob, character.Variables);
}
});
variablesParser.RegisterKeyword("list", ParserHelpers.IgnoreItem);
variablesParser.IgnoreAndLogUnregisteredItems();
variablesParser.ParseStream(reader);
if (character.Variables.ContainsKey("bald")) { // TODO: check if antigonus is converted bald

Check warning on line 159 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

Check warning on line 159 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

Check warning on line 159 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / Upload development build (linux-x64)

character.IsBald = true;
// Remove the "bald" flag to save memory.
character.Variables.Remove("bald");
}
}

private static void ParseCharacterVariable(string blob, IdObjectCollection<string, Variable> variables) {
string? name = null;
int? tick = null;
string? type = null;

var blobParser = new Parser();

// TODO: use CharacterVariable<T> struct here

Check warning on line 173 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

Check warning on line 173 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

Check warning on line 173 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / Upload development build (linux-x64)


blobParser.RegisterKeyword("flag", blobReader => name = string.Intern(blobReader.GetString()));
blobParser.RegisterKeyword("data", dataReader => {
var variableDataParser = new Parser();
variableDataParser.RegisterKeyword("type", typeReader => {
type = typeReader.GetString();
});
// TODO: also handle "tick" (days remaining)

Check warning on line 181 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

Check warning on line 181 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

Check warning on line 181 in ImperatorToCK3/Imperator/Characters/Character.cs

View workflow job for this annotation

GitHub Actions / Upload development build (linux-x64)

variableDataParser.RegisterKeyword("tick", tickReader => tick = tickReader.GetInt());
variableDataParser.RegisterKeyword("identity", valueReader => {
// At this point we know everything we need, so we can add the variable to the collection right after reading its value.
if (name is null) {
Logger.Warn("Can't store character variable without knowing its name!");
return;
}
if (type is null) {
Logger.Warn("Can't store character variable without knowing its type!");
}
switch (type) {
case "boolean":
variables.Add(new Variable(name, valueReader.GetBool()));
break;
case "value":
// This represents a real number.
// The game uses fixed point arithmetic, for example:
// 12.34 is stores as 12.34 * 100000 = 1234000.
// Negative values:
// -12.34 is stored as 2^64 - (12.34 * 100000) = 18446744073708317616.
var ulongValue = valueReader.GetULong();
var signedValue = unchecked((long)ulongValue);
var realValue = signedValue / 100000.0;
variables.Add(new Variable(name, realValue));
break;
default:
Logger.Warn($"Unrecognized character variable type: {type}!");
break;
}
});
variablesParser.RegisterKeyword("list", ParserHelpers.IgnoreItem);
variablesParser.IgnoreAndLogUnregisteredItems();
variablesParser.ParseStream(reader);
character.Variables = variables.ToImmutableHashSet();
variableDataParser.IgnoreAndLogUnregisteredItems();
variableDataParser.ParseStream(dataReader);
});
blobParser.IgnoreUnregisteredItems();

var blobReader = new BufferedReader(blob);
blobParser.ParseStream(blobReader);
}

private static void SetCharacterName(Character character, BufferedReader reader) {
var characterName = new CharacterName(reader);
character.Name = characterName.Name;
Expand Down Expand Up @@ -198,7 +254,7 @@
}
character.Unborns = [.. unborns];
}

public static Character Parse(BufferedReader reader, string idString, GenesDB? genesDB) {
var parser = new Parser(implicitVariableHandling: false);
var parsedCharacter = new Character(ulong.Parse(idString));
Expand Down
4 changes: 4 additions & 0 deletions ImperatorToCK3/Imperator/Provinces/ProvinceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using ImperatorToCK3.CommonUtils;
using ImperatorToCK3.Imperator.Countries;
using ImperatorToCK3.Imperator.States;
using System;
using System.Linq;

namespace ImperatorToCK3.Imperator.Provinces;
Expand Down Expand Up @@ -53,6 +54,9 @@ static Province() {
var buildingsList = reader.GetInts();
parsedProvince.BuildingCount = (uint)buildingsList.Sum();
});
provinceParser.RegisterKeyword("variables", reader => {
throw new NotImplementedException("TODO: convert variables to CK3");
});
provinceParser.IgnoreAndStoreUnregisteredItems(IgnoredTokens);
}

Expand Down
Loading