diff --git a/ImperatorToCK3.UnitTests/CK3/Religions/HolySiteTests.cs b/ImperatorToCK3.UnitTests/CK3/Religions/HolySiteTests.cs index 834e79c74..c7c02f30d 100644 --- a/ImperatorToCK3.UnitTests/CK3/Religions/HolySiteTests.cs +++ b/ImperatorToCK3.UnitTests/CK3/Religions/HolySiteTests.cs @@ -3,7 +3,7 @@ using commonItems.Serialization; using ImperatorToCK3.CK3.Religions; using ImperatorToCK3.CK3.Titles; -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using System.Collections.Generic; using Xunit; @@ -58,7 +58,7 @@ public void HolySiteCanBeConstructedForBaronyAndFaithWithEffects() { var titlesReader = new BufferedReader("c_county = { b_barony = { province = 1 } }"); titles.LoadTitles(titlesReader); - var holySiteEffectMapper = new HolySiteEffectMapper("TestFiles/configurables/holy_site_effect_mappings.txt"); + var holySiteEffectMapper = new ModifierMapper("TestFiles/configurables/holy_site_effect_mappings.txt"); var imperatorEffects = new OrderedDictionary { {"discipline", 0.2f}, // will be converted to knight_effectiveness_mult with factor of 10 {"unmapped_effect", 1f}, // will be skipped diff --git a/ImperatorToCK3.UnitTests/CK3/Religions/ReligionCollectionTests.cs b/ImperatorToCK3.UnitTests/CK3/Religions/ReligionCollectionTests.cs index 30423d7d4..5f9160047 100644 --- a/ImperatorToCK3.UnitTests/CK3/Religions/ReligionCollectionTests.cs +++ b/ImperatorToCK3.UnitTests/CK3/Religions/ReligionCollectionTests.cs @@ -6,7 +6,7 @@ using ImperatorToCK3.CK3.Religions; using ImperatorToCK3.CK3.Titles; using ImperatorToCK3.Imperator.Pops; -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using System; using System.Linq; using Xunit; @@ -155,7 +155,7 @@ Province GenerateCK3AndImperatorProvinceWithPops(ulong provId, int popCount, boo religions.DetermineHolySites( provinces, imperatorReligions, - new HolySiteEffectMapper("TestFiles/HolySiteEffectMapperTests/mappings.txt"), + new ModifierMapper("TestFiles/HolySiteEffectMapperTests/mappings.txt"), new Date("476.1.1") ); diff --git a/ImperatorToCK3.UnitTests/Mappers/HolySiteEffect/HolySiteEffectMapperTests.cs b/ImperatorToCK3.UnitTests/Mappers/HolySiteEffect/HolySiteEffectMapperTests.cs index 8dabac7e2..902c2ee37 100644 --- a/ImperatorToCK3.UnitTests/Mappers/HolySiteEffect/HolySiteEffectMapperTests.cs +++ b/ImperatorToCK3.UnitTests/Mappers/HolySiteEffect/HolySiteEffectMapperTests.cs @@ -1,4 +1,4 @@ -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using System.Collections.Generic; using Xunit; @@ -8,7 +8,7 @@ public class HolySiteEffectMapperTests { [Theory, MemberData(nameof(TestData))] public void MapperReturnsCorrectValues(string imperatorEffect, double imperatorValue, KeyValuePair? match) { const string mappingsFilePath = "TestFiles/HolySiteEffectMapperTests/mappings.txt"; - var mapper = new HolySiteEffectMapper(mappingsFilePath); + var mapper = new ModifierMapper(mappingsFilePath); Assert.Equal(match, mapper.Match(imperatorEffect, imperatorValue)); } diff --git a/ImperatorToCK3/CK3/Characters/CharacterCollection.cs b/ImperatorToCK3/CK3/Characters/CharacterCollection.cs index 932ef47c2..33ce16eac 100644 --- a/ImperatorToCK3/CK3/Characters/CharacterCollection.cs +++ b/ImperatorToCK3/CK3/Characters/CharacterCollection.cs @@ -2,15 +2,20 @@ using commonItems.Collections; using commonItems.Localization; using ImperatorToCK3.CK3.Armies; +using ImperatorToCK3.CK3.Modifiers; using ImperatorToCK3.CK3.Cultures; using ImperatorToCK3.CK3.Dynasties; using ImperatorToCK3.CK3.Titles; using ImperatorToCK3.CommonUtils; using ImperatorToCK3.CommonUtils.Map; using ImperatorToCK3.Imperator.Armies; +using ImperatorToCK3.Imperator.Provinces; +using ImperatorToCK3.Imperator.Religions; using ImperatorToCK3.Imperator.Characters; +using ImperatorToCK3.Mappers.Artifact; using ImperatorToCK3.Mappers.Culture; using ImperatorToCK3.Mappers.DeathReason; +using ImperatorToCK3.Mappers.Modifier; using ImperatorToCK3.Mappers.Nickname; using ImperatorToCK3.Mappers.Province; using ImperatorToCK3.Mappers.Religion; @@ -662,6 +667,195 @@ Configuration config Logger.IncrementProgress(); } + public void ImportArtifacts( + ProvinceCollection irProvinces, + ProvinceMapper provinceMapper, + Title.LandedTitles titles, + TreasureManager treasureManager, + ModifierMapper modifierMapper, + ModifierCollection ck3Modifiers, + LocDB irLocDB, + CK3LocDB ck3LocDB, + Date date + ) { + Logger.Info("Importing Imperator artifacts..."); + var ck3CharacterIdToTreasureIdsListDict = new Dictionary>(); + + foreach (var irProvince in irProvinces) { + var ck3Provinces = provinceMapper.GetCK3ProvinceNumbers(irProvince.Id); + if (ck3Provinces.Count == 0) { + continue; + } + var primaryCK3ProvinceId = ck3Provinces.First(); + + string? ownerId = null; + + var barony = titles.GetBaronyForProvince(primaryCK3ProvinceId); + if (barony is null) { + Logger.Warn($"Can't find barony for province {primaryCK3ProvinceId}!"); + continue; + } + var baronyHolderId = barony.GetHolderId(date); + if (baronyHolderId != "0") { + ownerId = baronyHolderId; + } else { + var county = titles.GetCountyForProvince(primaryCK3ProvinceId); + if (county is null) { + Logger.Warn($"Can't find county for province {primaryCK3ProvinceId}!"); + continue; + } + var countyHolderId = county.GetHolderId(date); + if (countyHolderId != "0") { + ownerId = countyHolderId; + } + } + + if (ownerId is null) { + Logger.Warn($"Can't find owner for province {primaryCK3ProvinceId}!"); + continue; + } + + if (ck3CharacterIdToTreasureIdsListDict.TryGetValue(ownerId, out var artifactList)) { + artifactList.AddRange(irProvince.TreasureIds); + } else { + ck3CharacterIdToTreasureIdsListDict[ownerId] = irProvince.TreasureIds.ToList(); + } + } + // TODO: also import artifacts not assigned to holy sites (search for treasures={ 225 226 } in save) + + // TODO: check if needed: Create visuals for artifacts. + /*var treasureIconNames = treasureManager + .Select(t => t.IconName) + .Distinct() + .ToList(); + foreach (var iconName in treasureIconNames) { + throw new NotImplementedException(); + // example: + // icon: artefact_icons_unique_artifact_cheese + // asset entity: ep1_western_pouch_basic_01_a_entity + // mesh: ep1_western_pouch_basic_01_a_mesh + }*/ + + var visualsMapper = new ArtifactMapper("configurables/artifact_map.txt"); + + var charactersFromImperator = this.Where(c => c.FromImperator).ToList(); + foreach (var character in charactersFromImperator) { + if (!ck3CharacterIdToTreasureIdsListDict.TryGetValue(character.Id, out var irArtifactIds)) { + continue; + } + + // TODO: try to use create_artifact_sculpture_babr_e_bayan_effect as base + foreach (var irArtifactId in irArtifactIds) { + var irArtifact = treasureManager[irArtifactId]; + ImportArtifact(character, irArtifact, modifierMapper, ck3Modifiers, visualsMapper, irLocDB, ck3LocDB, date); + } + + + + /* + * # Create the artifact + create_artifact = { + name = artifact_sculpture_armor_babr_name + description = artifact_sculpture_armor_babr + type = sculpture + template = babr_template + visuals = sculpture_babr_e_bayan + wealth = scope:wealth + quality = scope:quality + history = { + type = created_before_history + } + modifier = babr_e_bayan_modifier + save_scope_as = newly_created_artifact + decaying = yes + } + + scope:newly_created_artifact = { + set_variable = { name = historical_unique_artifact value = yes } + set_variable = babr_e_bayan + save_scope_as = epic + } + */ + } + + Logger.IncrementProgress(); + } + + private void ImportArtifact(Character character, Treasure irArtifact, ModifierMapper modifierMapper, ModifierCollection ck3Modifiers, ArtifactMapper artifactMapper, LocDB irLocDB, CK3LocDB ck3LocDB, Date date) { + var visualAndType = artifactMapper.GetVisualAndType(irArtifact.Key, irArtifact.IconName); + if (visualAndType is null) { + Logger.Warn($"Can't find a match for I:R artifact key {irArtifact.Key} and icon {irArtifact.IconName}!"); + return; + } + var (ck3Visual, ck3Type) = visualAndType.Value; + + var ck3ArtifactName = $"IRToCK3_artifact_{irArtifact.Key}_{irArtifact.Id}"; + var irNameLoc = irLocDB.GetLocBlockForKey(irArtifact.Key); + if (irNameLoc is null) { + Logger.Warn($"Can't find name loc for artifact {irArtifact.Key}!"); + } else { + var artifactNameLocBlock = ck3LocDB.GetOrCreateLocBlock(ck3ArtifactName); + artifactNameLocBlock.CopyFrom(irNameLoc); + } + + var ck3DescKey = $"{ck3ArtifactName}_desc"; + var irDescLoc = irLocDB.GetLocBlockForKey(irArtifact.Key + "_desc"); + if (irDescLoc is null) { + Logger.Warn($"Can't find description loc for artifact {irArtifact.Key}!"); + } else { + var descLocBlock = ck3LocDB.GetOrCreateLocBlock(ck3DescKey); + descLocBlock.CopyFrom(irDescLoc); + } + + var artifactScope = $"newly_created_artifact_{irArtifact.Id}"; + + var ck3ModifierEffects = new Dictionary(); + foreach (var (irEffect, irEffectValue) in irArtifact.StateModifiers) { + var match = modifierMapper.Match(irEffect, irEffectValue); + if (match is null) { + Logger.Warn($"Can't find CK3 modifier for Imperator modifier {irEffect}!"); + continue; + } + ck3ModifierEffects[match.Value.Key] = match.Value.Value; + } + foreach (var (irEffect, irEffectValue) in irArtifact.CharacterModifiers) { + var match = modifierMapper.Match(irEffect, irEffectValue); + if (match is null) { + Logger.Warn($"Can't find CK3 modifier for Imperator modifier {irEffect}!"); + continue; + } + ck3ModifierEffects[match.Value.Key] = match.Value.Value; + } + var ck3ModifierId = $"{ck3ArtifactName}_modifier"; + ck3Modifiers.Add(new Modifier(ck3ModifierId, ck3ModifierEffects)); + + string createArtifactEffect = $$""" + set_artifact_rarity_illustrious = yes + create_artifact = { + name = {{ ck3ArtifactName }} + description = {{ ck3DescKey }} + type = {{ ck3Type }} + # template = babr_template # TODO: check if needed + visuals = {{ ck3Visual }} + wealth = scope:wealth + quality = scope:quality + history = { + type = created_before_history + } + modifier = {{ ck3ModifierId }} + save_scope_as = {{ artifactScope }} + decaying = yes + } + scope:{{ artifactScope }} = { + set_variable = { + name = historical_unique_artifact + value = yes + } + } + """; + character.History.AddFieldValue(date, "effects", "effect", createArtifactEffect); + } + public void GenerateSuccessorsForOldCharacters(Title.LandedTitles titles, CultureCollection cultures, Date irSaveDate, Date ck3BookmarkDate, ulong randomSeed) { Logger.Info("Generating successors for old characters..."); diff --git a/ImperatorToCK3/CK3/Modifiers/Modifier.cs b/ImperatorToCK3/CK3/Modifiers/Modifier.cs new file mode 100644 index 000000000..6d3c7e377 --- /dev/null +++ b/ImperatorToCK3/CK3/Modifiers/Modifier.cs @@ -0,0 +1,25 @@ +using commonItems.Collections; +using System.Collections.Generic; +using System.Text; + +namespace ImperatorToCK3.CK3.Modifiers; + +public class Modifier : IIdentifiable { + public string Id { get; } + private readonly Dictionary effects = new(); + + public Modifier(string id, IDictionary effects) { + Id = id; + this.effects = new Dictionary(effects); + } + + public override string ToString() { + var output = new StringBuilder(); + output.AppendLine($"{Id} = {{"); + foreach (var effect in effects) { + output.AppendLine($"\t{effect.Key} = {effect.Value}"); + } + output.AppendLine("}"); + return output.ToString(); + } +} \ No newline at end of file diff --git a/ImperatorToCK3/CK3/Modifiers/ModifierCollection.cs b/ImperatorToCK3/CK3/Modifiers/ModifierCollection.cs new file mode 100644 index 000000000..db44a82f4 --- /dev/null +++ b/ImperatorToCK3/CK3/Modifiers/ModifierCollection.cs @@ -0,0 +1,14 @@ +using commonItems.Collections; +using System.Text; + +namespace ImperatorToCK3.CK3.Modifiers; + +public class ModifierCollection : IdObjectCollection { + public override string ToString() { + var output = new StringBuilder(); + foreach (var modifier in this) { + output.AppendLine(modifier.ToString()); + } + return output.ToString(); + } +} \ No newline at end of file diff --git a/ImperatorToCK3/CK3/Religions/HolySite.cs b/ImperatorToCK3/CK3/Religions/HolySite.cs index 45e548667..2a613bc49 100644 --- a/ImperatorToCK3/CK3/Religions/HolySite.cs +++ b/ImperatorToCK3/CK3/Religions/HolySite.cs @@ -3,7 +3,7 @@ using commonItems.Serialization; using commonItems.SourceGenerators; using ImperatorToCK3.CK3.Titles; -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using System.Collections.Generic; namespace ImperatorToCK3.CK3.Religions; @@ -82,10 +82,10 @@ public HolySite( Faith faith, Title.LandedTitles titles, OrderedDictionary imperatorEffects, - HolySiteEffectMapper holySiteEffectMapper + ModifierMapper modifierMapper ) : this(barony, faith, titles) { foreach (var (effect, value) in imperatorEffects) { - var ck3EffectOpt = holySiteEffectMapper.Match(effect, value); + var ck3EffectOpt = modifierMapper.Match(effect, value); if (ck3EffectOpt is not { } ck3Effect) { continue; } diff --git a/ImperatorToCK3/CK3/Religions/ReligionCollection.cs b/ImperatorToCK3/CK3/Religions/ReligionCollection.cs index 95f997c1f..0ade7931f 100644 --- a/ImperatorToCK3/CK3/Religions/ReligionCollection.cs +++ b/ImperatorToCK3/CK3/Religions/ReligionCollection.cs @@ -6,7 +6,7 @@ using ImperatorToCK3.CK3.Cultures; using ImperatorToCK3.CK3.Titles; using ImperatorToCK3.CK3.Provinces; -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using Open.Collections; using System; using System.Collections.Generic; @@ -201,7 +201,7 @@ private HolySite GenerateHolySiteForBarony( Faith ck3Faith, ProvinceCollection ck3Provinces, Imperator.Religions.ReligionCollection imperatorReligions, - HolySiteEffectMapper holySiteEffectMapper + ModifierMapper modifierMapper ) { var imperatorProvince = GetImperatorProvinceForBarony(barony, ck3Provinces); if (imperatorProvince is null) { @@ -223,13 +223,13 @@ HolySiteEffectMapper holySiteEffectMapper } } - return new HolySite(barony, ck3Faith, landedTitles, imperatorModifiers, holySiteEffectMapper); + return new HolySite(barony, ck3Faith, landedTitles, imperatorModifiers, modifierMapper); } public void DetermineHolySites( ProvinceCollection ck3Provinces, Imperator.Religions.ReligionCollection imperatorReligions, - HolySiteEffectMapper holySiteEffectMapper, + ModifierMapper modifierMapper, Date date ) { var provincesByFaith = GetProvincesFromImperatorByFaith(ck3Provinces, date); @@ -258,7 +258,7 @@ Date date faith, ck3Provinces, imperatorReligions, - holySiteEffectMapper + modifierMapper ); if (HolySites.ContainsKey(newHolySiteInSameBarony.Id)) { Logger.Warn($"Created duplicate holy site: {newHolySiteInSameBarony.Id}!"); @@ -279,7 +279,7 @@ Date date faith, ck3Provinces, imperatorReligions, - holySiteEffectMapper + modifierMapper ); if (HolySites.ContainsKey(replacementSite.Id)) { Logger.Warn($"Created duplicate holy site: {replacementSite.Id}!"); diff --git a/ImperatorToCK3/CK3/World.cs b/ImperatorToCK3/CK3/World.cs index abe53e73a..8d8f8ae18 100644 --- a/ImperatorToCK3/CK3/World.cs +++ b/ImperatorToCK3/CK3/World.cs @@ -8,6 +8,7 @@ using ImperatorToCK3.CK3.Diplomacy; using ImperatorToCK3.CK3.Dynasties; using ImperatorToCK3.CK3.Legends; +using ImperatorToCK3.CK3.Modifiers; using ImperatorToCK3.CK3.Provinces; using ImperatorToCK3.CK3.Religions; using ImperatorToCK3.CK3.Titles; @@ -20,7 +21,7 @@ using ImperatorToCK3.Mappers.Culture; using ImperatorToCK3.Mappers.DeathReason; using ImperatorToCK3.Mappers.Government; -using ImperatorToCK3.Mappers.HolySiteEffect; +using ImperatorToCK3.Mappers.Modifier; using ImperatorToCK3.Mappers.Nickname; using ImperatorToCK3.Mappers.Province; using ImperatorToCK3.Mappers.Region; @@ -50,6 +51,7 @@ internal sealed class World { public CK3LocDB LocDB { get; } = []; private ScriptValueCollection ScriptValues { get; } = new(); public NamedColorCollection NamedColors { get; } = new(); + public ModifierCollection Modifiers { get; } = new(); public CharacterCollection Characters { get; } = new(); public DynastyCollection Dynasties { get; } = []; public HouseCollection DynastyHouses { get; } = []; @@ -397,17 +399,30 @@ public World(Imperator.World impWorld, Configuration config, Thread? irCoaExtrac // Now that the title history is basically done, convert officials as council members and courtiers. LandedTitles.ImportImperatorGovernmentOffices(impWorld.JobsDB.OfficeJobs, Religions, impWorld.EndDate); + var modifierMapper = new ModifierMapper("configurables/holy_site_effect_mappings.txt"); + 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.DetermineHolySites(Provinces, impWorld.Religions, modifierMapper, config.CK3BookmarkDate); Religions.GenerateMissingReligiousHeads(LandedTitles, Characters, Provinces, Cultures, config.CK3BookmarkDate); Logger.IncrementProgress(); }, + () => Characters.ImportArtifacts( + impWorld.Provinces, + provinceMapper, + LandedTitles, + impWorld.TreasureManager, + modifierMapper, + Modifiers, + impWorld.LocDB, + LocDB, + CorrectedDate + ), + () => { LegendSeeds.LoadSeeds(ModFS); LegendSeeds.RemoveAnachronisticSeeds("configurables/legend_seeds_to_remove.txt"); diff --git a/ImperatorToCK3/Converter.cs b/ImperatorToCK3/Converter.cs index 2228fc0db..88e5258e9 100644 --- a/ImperatorToCK3/Converter.cs +++ b/ImperatorToCK3/Converter.cs @@ -6,7 +6,6 @@ namespace ImperatorToCK3; internal static class Converter { public static void ConvertImperatorToCK3(ConverterVersion converterVersion) { Logger.Progress(0); - DebugInfo.LogEverything(); SystemUtils.TryCreateFolder("temp"); var config = new Configuration(converterVersion); diff --git a/ImperatorToCK3/Data_Files/configurables/artifact_map.txt b/ImperatorToCK3/Data_Files/configurables/artifact_map.txt new file mode 100644 index 000000000..0a864f148 --- /dev/null +++ b/ImperatorToCK3/Data_Files/configurables/artifact_map.txt @@ -0,0 +1,81 @@ +# Usage: +# irTreasure = Imperator treasure ID +# irIcon = Imperator treasure icon +# ck3Visual = CK3 visual ID (from common/artifacts/visuals) +# ck3Type = CK3 artifact type (from common/artifacts/types) +# Every link has to have either irTreasure or irIcon set. + +# Imperator treasures list with icons: https://imperator.paradoxwikis.com/Treasure#List_of_treasures + +link = { + irTreasure = treasure_armor_of_alexander + ck3Visual = pedestal_alexander_armor + ck3Type = pedestal +} + +link = { + irIcon = treasure_urn_1 + ck3Visual = urn + ck3Type = urn +} +link = { + irIcon = treasure_urn_2 + ck3Visual = urn + ck3Type = urn +} +link = { + irIcon = treasure_urn_3 + ck3Visual = urn + ck3Type = urn +} +link = { + irIcon = treasure_architecture + ck3Visual = statue + ck3Type = sculpture +} +link = { + irIcon = treasure_architecture_hoa + ck3Visual = statue + ck3Type = sculpture +} +link = { + irIcon = treasure_armor + ck3Visual = armor + ck3Type = armor_mail +} +link = { + irIcon = treasure_armor_hoa + ck3Visual = armor + ck3Type = armor_scale +} +link = { + irIcon = treasure_chest + ck3Visual = chest + ck3Type = chest +} +link = { + irIcon = treasure_it_sacred_stone_of_emesa + ck3Visual = chest # We don't have a special visual for this as of CK3 1.14. + ck3Type = chest +} +link = { + irIcon = cr_treasure_skins_of_the_gorilla_03 + ck3Visual = large_animal_hide + ck3Type = animal_hide_big +} +#link = { # disabled because sarhophagi don't seem to fit well as CK3 court artifacts, and we have no visuals for them +# irIcon = treasure_sarcophagus +# ck3Visual = chest # there is no sarcophagus in CK3 as of 1.8 +# ck3Type = chest +#} +#link = { # disabled because sarhophagi don't seem to fit well as CK3 court artifacts, and we have no visuals for them +# irIcon = treasure_sarcophagus_hoa +# ck3Visual = chest # there is no sarcophagus in CK3 as of 1.8 +# ck3Type = chest +#} +link = { + irIcon = treasure_sword + ck3Visual = sword + ck3Type = sword +} + diff --git a/ImperatorToCK3/Data_Files/configurables/holy_site_effect_mappings.txt b/ImperatorToCK3/Data_Files/configurables/holy_site_effect_mappings.txt index 0d33f00e4..dc023986f 100644 --- a/ImperatorToCK3/Data_Files/configurables/holy_site_effect_mappings.txt +++ b/ImperatorToCK3/Data_Files/configurables/holy_site_effect_mappings.txt @@ -1,7 +1,6 @@ # -# Mapping of Imperator Religion Modifier + Deity Passive Effect -> CK3 Effect -# -# This is used for conversion of Holy Sites. +# Originally mappings of I:R Religion Modifier + Deity Passive Effect -> CK3 Effect (meant to be used for conversion of Holy Sites). +# Now also used for conversion of I:R treasures to CK3 artifacts. # link = { ir = global_citizen_happyness ck3 = county_opinion_add factor = 100 } @@ -70,4 +69,8 @@ link = { ir = fabricate_claim_speed ck3 = tyranny_loss_mult factor = 1 } link = { ir = war_exhaustion ck3 = tyranny_loss_mult factor = -10 } link = { ir = monthly_legitimacy ck3 = monthly_prestige_gain_mult factor = 1 } link = { ir = monthly_experience_gain ck3 = monthly_martial_lifestyle_xp_gain_mult factor = 1 } -link = { ir = monthly_wage_modifier ck3 = vassal_opinion factor = -100 } \ No newline at end of file +link = { ir = monthly_wage_modifier ck3 = vassal_opinion factor = -100 } + +# additional mappings for artifacts +link = { ir = local_citizen_happyness ck3 = county_opinion_add factor = 100 } +# TODO: add mappings for the rest \ No newline at end of file diff --git a/ImperatorToCK3/Imperator/Provinces/Province.cs b/ImperatorToCK3/Imperator/Provinces/Province.cs index 2e12f8251..afa2b7bb4 100644 --- a/ImperatorToCK3/Imperator/Provinces/Province.cs +++ b/ImperatorToCK3/Imperator/Provinces/Province.cs @@ -22,6 +22,8 @@ internal sealed partial class Province : IIdentifiable { public bool Fort { get; set; } = false; public bool IsHolySite => HolySiteId is not null; public ulong? HolySiteId { get; set; } = null; + private List treasureIds = new(); + public IReadOnlyCollection TreasureIds => treasureIds; public ulong? HoldingOwnerId { get; set; } = null; public uint BuildingCount { get; set; } = 0; public double CivilizationValue { get; set; } = 0; diff --git a/ImperatorToCK3/Imperator/Provinces/ProvinceFactory.cs b/ImperatorToCK3/Imperator/Provinces/ProvinceFactory.cs index bcbd2324e..cd0c5065d 100644 --- a/ImperatorToCK3/Imperator/Provinces/ProvinceFactory.cs +++ b/ImperatorToCK3/Imperator/Provinces/ProvinceFactory.cs @@ -49,6 +49,18 @@ static Province() { parsedProvince.HolySiteId = holySiteId; } }); + provinceParser.RegisterKeyword("treasure_slots", treasureSlotsReader => { + var treasureSlotsParser = new Parser(); + treasureSlotsParser.RegisterKeyword("treasures", treasuresReader => { + // 4294967295 equals (2^32 − 1) and is the default value. + // Any other value is an ID of a treasure. + parsedProvince.treasureIds = treasuresReader.GetULongs() + .Where(id => id != 4294967295) + .ToList(); + }); + treasureSlotsParser.IgnoreAndLogUnregisteredItems(); + treasureSlotsParser.ParseStream(treasureSlotsReader); + }); provinceParser.RegisterKeyword("buildings", reader => { var buildingsList = reader.GetInts(); parsedProvince.BuildingCount = (uint)buildingsList.Sum(); diff --git a/ImperatorToCK3/Imperator/Religions/Treasure.cs b/ImperatorToCK3/Imperator/Religions/Treasure.cs new file mode 100644 index 000000000..5ccd3c395 --- /dev/null +++ b/ImperatorToCK3/Imperator/Religions/Treasure.cs @@ -0,0 +1,51 @@ +using commonItems; +using commonItems.Collections; +using ImperatorToCK3.Exceptions; +using System.Collections.Generic; + +namespace ImperatorToCK3.Imperator.Religions; + +public sealed class Treasure : IIdentifiable { + public ulong Id { get; } + public string Key { get; private set; } + public string IconName { get; private set; } + + private readonly Dictionary stateModifiers = []; + public IReadOnlyDictionary StateModifiers => stateModifiers; + private readonly Dictionary characterModifiers = []; + public IReadOnlyDictionary CharacterModifiers => characterModifiers; + + public Treasure(ulong id, BufferedReader treasureReader) { + Id = id; + + string? key = null; + string? iconName = null; + + var parser = new Parser(); + parser.RegisterKeyword("key", reader => key = reader.GetString()); + parser.RegisterKeyword("icon", reader => iconName = reader.GetString()); + parser.RegisterKeyword("state_modifier", reader => { + var stateModifierParser = new Parser(); + stateModifierParser.RegisterKeyword("name", ParserHelpers.IgnoreItem); + stateModifierParser.RegisterRegex(CommonRegexes.String, (modifierReader, name) => { + stateModifiers[name] = modifierReader.GetDouble(); + }); + stateModifierParser.IgnoreAndLogUnregisteredItems(); + stateModifierParser.ParseStream(reader); + }); + parser.RegisterKeyword("character_modifier", reader => { + var characterModifierParser = new Parser(); + characterModifierParser.RegisterKeyword("name", ParserHelpers.IgnoreItem); + characterModifierParser.RegisterRegex(CommonRegexes.String, (modifierReader, name) => { + characterModifiers[name] = modifierReader.GetDouble(); + }); + characterModifierParser.IgnoreAndLogUnregisteredItems(); + characterModifierParser.ParseStream(reader); + }); + parser.IgnoreAndLogUnregisteredItems(); + parser.ParseStream(treasureReader); + + Key = key ?? throw new ConverterException($"key was not defined for treasure {id}!"); + IconName = iconName ?? throw new ConverterException($"icon was not defined for treasure {id}!"); + } +} \ No newline at end of file diff --git a/ImperatorToCK3/Imperator/Religions/TreasureManager.cs b/ImperatorToCK3/Imperator/Religions/TreasureManager.cs new file mode 100644 index 000000000..07ec6f087 --- /dev/null +++ b/ImperatorToCK3/Imperator/Religions/TreasureManager.cs @@ -0,0 +1,22 @@ +using commonItems; +using commonItems.Collections; + +namespace ImperatorToCK3.Imperator.Religions; + +public sealed class TreasureManager : IdObjectCollection { + public void LoadTreasures(BufferedReader treasureManagerReader) { + var parser = new Parser(); + parser.RegisterKeyword("database", LoadTreasuresFromDatabase); + parser.IgnoreAndLogUnregisteredItems(); + parser.ParseStream(treasureManagerReader); + } + + private void LoadTreasuresFromDatabase(BufferedReader treasureDatabaseReader) { + var parser = new Parser(); + parser.RegisterRegex(CommonRegexes.Integer, (reader, idStr) => { + AddOrReplace(new Treasure(ulong.Parse(idStr), reader)); + }); + parser.IgnoreAndLogUnregisteredItems(); + parser.ParseStream(treasureDatabaseReader); + } +} \ No newline at end of file diff --git a/ImperatorToCK3/Imperator/World.cs b/ImperatorToCK3/Imperator/World.cs index f1d26997f..f8102be94 100644 --- a/ImperatorToCK3/Imperator/World.cs +++ b/ImperatorToCK3/Imperator/World.cs @@ -68,6 +68,7 @@ internal partial class World { public CulturesDB CulturesDB { get; } = []; public ReligionCollection Religions { get; private set; } private GenesDB genesDB = new(); + public TreasureManager TreasureManager { get; } = new(); public InventionsDB InventionsDB { get; } = new(); public ColorFactory ColorFactory { get; } = new(); @@ -384,6 +385,11 @@ private void ParseSave(Configuration config, ConverterVersion converterVersion, parser.RegisterKeyword("tutorial_disable", ParserHelpers.IgnoreItem); var playerCountriesToLog = new OrderedSet(); parser.RegisterKeyword("played_country", LoadPlayerCountries(playerCountriesToLog)); + parser.RegisterKeyword("treasure_manager", reader => { + Logger.Info("Loading treasures..."); + TreasureManager.LoadTreasures(reader); + Logger.Debug($"{TreasureManager.Count} treasures loaded."); + }); parser.IgnoreAndStoreUnregisteredItems(ignoredTokens); parser.ParseStream(ProcessSave(config.SaveGamePath)); diff --git a/ImperatorToCK3/Mappers/Artifact/ArtifactMapper.cs b/ImperatorToCK3/Mappers/Artifact/ArtifactMapper.cs new file mode 100644 index 000000000..146a283f6 --- /dev/null +++ b/ImperatorToCK3/Mappers/Artifact/ArtifactMapper.cs @@ -0,0 +1,69 @@ +using commonItems; +using System.Collections.Generic; + +namespace ImperatorToCK3.Mappers.Artifact; + +public class ArtifactMapper { + public ArtifactMapper(string mappingsPath) { + Logger.Info("Loading artifact mappings..."); + var parser = new Parser(); + RegisterKeys(parser); + parser.ParseFile(mappingsPath); + + Logger.Info($"Loaded {mappings.Count} artifact mappings."); + + // TODO: implement checking if the ck3 visuals actually exist. We need to read the CK3 visuals files for that. + // TODO: also do the same for CK3 artifact types. + } + + private void RegisterKeys(Parser parser) { + parser.RegisterKeyword("link", linkReader => { + string? ck3Visual = null; + string? ck3Type = null; + var irTreasureIds = new List(); + var irIconIds = new List(); + + var linkParser = new Parser(); + linkParser.RegisterKeyword("ck3Visual", reader => { + ck3Visual = reader.GetString(); + }); + linkParser.RegisterKeyword("ck3Type", reader => { + ck3Type = reader.GetString(); + }); + linkParser.RegisterKeyword("irTreasure" , reader => { + irTreasureIds.Add(reader.GetString()); + }); + linkParser.RegisterKeyword("irIcon" , reader => { + irIconIds.Add(reader.GetString()); + }); + linkParser.IgnoreAndLogUnregisteredItems(); + linkParser.ParseStream(linkReader); + + if (ck3Visual is null || ck3Type is null) { + return; + } + + var mapping = new ArtifactMapping() { + CK3Visual = ck3Visual, CK3Type = ck3Type, IRTreasureIds = irTreasureIds, IRIconIds = irIconIds + }; + mappings.Add(mapping); + }); + } + + public (string?, string?)? GetVisualAndType(string irArtifactId, string irIconId) { + foreach (var mapping in mappings) { + if (mapping.IRTreasureIds.Count > 0 && !mapping.IRTreasureIds.Contains(irArtifactId)) { + continue; + } + if (mapping.IRIconIds.Count > 0 && !mapping.IRIconIds.Contains(irIconId)) { + continue; + } + + return (mapping.CK3Visual, mapping.CK3Type); + } + + return null; + } + + private readonly List mappings = []; +} \ No newline at end of file diff --git a/ImperatorToCK3/Mappers/Artifact/ArtifactMapping.cs b/ImperatorToCK3/Mappers/Artifact/ArtifactMapping.cs new file mode 100644 index 000000000..193e91f12 --- /dev/null +++ b/ImperatorToCK3/Mappers/Artifact/ArtifactMapping.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace ImperatorToCK3.Mappers.Artifact; + +internal record ArtifactMapping { + public string CK3Visual { get; init; } = string.Empty; + public string CK3Type { get; init; } = string.Empty; + + public List IRTreasureIds { get; init; } = []; + public List IRIconIds { get; init; } = []; +} \ No newline at end of file diff --git a/ImperatorToCK3/Mappers/HolySiteEffect/HolySiteEffectMapper.cs b/ImperatorToCK3/Mappers/Modifier/ModifierMapper.cs similarity index 78% rename from ImperatorToCK3/Mappers/HolySiteEffect/HolySiteEffectMapper.cs rename to ImperatorToCK3/Mappers/Modifier/ModifierMapper.cs index 640644931..da0b9fc30 100644 --- a/ImperatorToCK3/Mappers/HolySiteEffect/HolySiteEffectMapper.cs +++ b/ImperatorToCK3/Mappers/Modifier/ModifierMapper.cs @@ -1,12 +1,12 @@ using commonItems; using System.Collections.Generic; -namespace ImperatorToCK3.Mappers.HolySiteEffect; +namespace ImperatorToCK3.Mappers.Modifier; -public sealed class HolySiteEffectMapper { +public sealed class ModifierMapper { private readonly Dictionary> effectMap = new(); // imperator effect, - public HolySiteEffectMapper(string mappingsFilePath) { + public ModifierMapper(string mappingsFilePath) { var parser = new Parser(); parser.RegisterKeyword("link", mappingReader => { string? ir = null; @@ -19,9 +19,11 @@ public HolySiteEffectMapper(string mappingsFilePath) { mappingParser.RegisterKeyword("factor", reader => factor = reader.GetDouble()); mappingParser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem); mappingParser.ParseStream(mappingReader); - - if (ir is null || ck3 is null) { - Logger.Warn($"Holy site effect mapping {ir} {ck3} {factor} has no ir or ck3 entry!"); + + if (ir is null) { + Logger.Warn($"Modifier mapping {ir} {ck3} {factor} has no {nameof(ir)} entry!"); + } else if (ck3 is null) { + Logger.Warn($"Modifier mapping {ir} {ck3} {factor} has no {nameof(ck3)} entry!"); } else { effectMap[ir] = new KeyValuePair(ck3, factor); } diff --git a/ImperatorToCK3/Outputter/WorldOutputter.cs b/ImperatorToCK3/Outputter/WorldOutputter.cs index e3a3f1863..dfdee720a 100644 --- a/ImperatorToCK3/Outputter/WorldOutputter.cs +++ b/ImperatorToCK3/Outputter/WorldOutputter.cs @@ -54,6 +54,8 @@ public static void OutputWorld(World ck3World, Imperator.World imperatorWorld, C CoatOfArmsOutputter.OutputCoas(outputPath, ck3World.LandedTitles, ck3World.Dynasties, ck3World.CK3CoaMapper), Task.Run(() => CoatOfArmsOutputter.CopyCoaPatterns(imperatorWorld.ModFS, outputPath)), + Task.Run(() => OutputModifiers(ck3World, outputPath)), + BookmarkOutputter.OutputBookmark(ck3World, config, ck3World.LocDB) ); @@ -218,6 +220,7 @@ private static void CreateFolders(string outputPath) { SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "landed_titles")); SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "legends", "legend_seeds")); SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "men_at_arms_types")); + SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "modifiers")); SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "named_colors")); SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "on_action")); SystemUtils.TryCreateFolder(Path.Combine(outputPath, "common", "religion", "holy_sites")); @@ -266,4 +269,12 @@ private static void OutputPlaysetInfo(World ck3World, string outputModName) { Logger.IncrementProgress(); } + + private static void OutputModifiers(World ck3World, string outputPath) { + Logger.Info("Outputting modifiers..."); + var fileOutputPath = Path.Combine(outputPath, "common/modifiers/IRToCK3_modifiers.txt"); + + using var modifiersWriter = new StreamWriter(fileOutputPath); + modifiersWriter.WriteLine(ck3World.Modifiers.ToString()); + } } \ No newline at end of file