Skip to content

Commit

Permalink
Technology conversion configurable through configurables/inventions_t…
Browse files Browse the repository at this point in the history
…o_innovations_map.txt (#1878)

part of #163
  • Loading branch information
IhateTrains authored Apr 14, 2024
1 parent 792d5ef commit f9889a6
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 8 deletions.
36 changes: 35 additions & 1 deletion ImperatorToCK3/CK3/Cultures/Culture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
using commonItems.Collections;
using commonItems.Colors;
using commonItems.Serialization;
using ImperatorToCK3.Mappers.Technology;
using Open.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

Expand All @@ -11,7 +14,7 @@ namespace ImperatorToCK3.CK3.Cultures;
public sealed class Culture : IIdentifiable<string>, IPDXSerializable {
public string Id { get; }
public Color Color { get; }
public OrderedSet<string> ParentCultureIds { get; set; } = new();
public OrderedSet<string> ParentCultureIds { get; set; }
public Pillar Heritage { get; }
public Pillar Language { get; }
private readonly OrderedSet<string> traditionIds;
Expand All @@ -20,6 +23,9 @@ public sealed class Culture : IIdentifiable<string>, IPDXSerializable {
public IReadOnlyCollection<NameList> NameLists => nameLists;
private readonly List<KeyValuePair<string, StringOfItem>> attributes;
public IReadOnlyCollection<KeyValuePair<string, StringOfItem>> Attributes => attributes;

private readonly List<string> innovationsFromImperator = [];
private readonly Dictionary<string, ushort> innovationProgressesFromImperator = [];

public Culture(string id, CultureData cultureData) {
Id = id;
Expand Down Expand Up @@ -63,6 +69,34 @@ public string Serialize(string indent, bool withBraces) {
return sb.ToString();
}

public void OutputHistory(string outputModPath, Date date) {
if (innovationsFromImperator.Count == 0 && innovationProgressesFromImperator.Count == 0) {
// Nothing to output.
return;
}

var historyPath = Path.Combine(outputModPath, "history/cultures", Id + ".txt");
using var historyWriter = File.CreateText(historyPath);
historyWriter.WriteLine("# This file was generated by the IRToCK3 converter.");

historyWriter.WriteLine($"{date} = {{");
foreach (var innovationId in innovationsFromImperator) {
historyWriter.WriteLine($"\tdiscover_innovation = {innovationId}");
}
foreach (var (innovationId, progress) in innovationProgressesFromImperator) {
historyWriter.WriteLine("\tadd_innovation_progress = {");
historyWriter.WriteLine($"\t\tculture_innovation = {innovationId}");
historyWriter.WriteLine($"\t\tprogress = {progress}");
historyWriter.WriteLine("\t}");
}
historyWriter.WriteLine("}");
}

public void ImportInnovationsFromImperator(ISet<string> irInventions, InnovationMapper innovationMapper) {
innovationsFromImperator.AddRange(innovationMapper.GetInnovations(irInventions));
innovationProgressesFromImperator.AddRange(innovationMapper.GetInnovationProgresses(irInventions));
}

public IEnumerable<string> MaleNames => NameLists.SelectMany(l => l.MaleNames);
public IEnumerable<string> FemaleNames => NameLists.SelectMany(l => l.FemaleNames);
}
47 changes: 47 additions & 0 deletions ImperatorToCK3/CK3/Cultures/CultureCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
using commonItems.Mods;
using Fernandezja.ColorHashSharp;
using ImperatorToCK3.CommonUtils;
using ImperatorToCK3.Imperator.Countries;
using ImperatorToCK3.Imperator.Inventions;
using ImperatorToCK3.Mappers.Culture;
using ImperatorToCK3.Mappers.Province;
using ImperatorToCK3.Mappers.Technology;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -151,6 +156,48 @@ public void LoadNameLists(ModFilesystem ck3ModFS) {
parser.ParseGameFolder("common/culture/name_lists", ck3ModFS, "txt", recursive: true, logFilePaths: true);
}

private string? GetCK3CultureIdForImperatorCountry(Country country, CultureMapper cultureMapper, ProvinceMapper provinceMapper) {
var irCulture = country.PrimaryCulture ?? country.Monarch?.Culture;
if (irCulture is null) {
Logger.Warn($"Failed to get primary or monarch culture for Imperator country {country.Tag}!");
return null;
}

ulong? irProvinceId = country.CapitalProvinceId ?? country.Monarch?.ProvinceId;
ulong? ck3ProvinceId = null;
if (irProvinceId.HasValue) {
ck3ProvinceId = provinceMapper.GetCK3ProvinceNumbers(irProvinceId.Value).FirstOrDefault();
}

return cultureMapper.Match(irCulture, ck3ProvinceId, irProvinceId, country.HistoricalTag);
}

public void ImportTechnology(CountryCollection countries, CultureMapper cultureMapper, ProvinceMapper provinceMapper, InventionsDB inventionsDB) { // TODO: test this

Check warning on line 175 in ImperatorToCK3/CK3/Cultures/CultureCollection.cs

View workflow job for this annotation

GitHub Actions / Upload development build (win-x64)

Check warning on line 175 in ImperatorToCK3/CK3/Cultures/CultureCollection.cs

View workflow job for this annotation

GitHub Actions / Upload development build (linux-x64)

Logger.Info("Converting Imperator inventions to CK3 innovations...");

var innovationMapper = new InnovationMapper();
innovationMapper.LoadLinksAndBonuses("configurables/inventions_to_innovations_map.txt");

// Group I:R countries by corresponding CK3 culture.
var countriesByCulture = countries.Select(c => new {
Country = c, CK3CultureId = GetCK3CultureIdForImperatorCountry(c, cultureMapper, provinceMapper),
})
.Where(c => c.CK3CultureId is not null)
.GroupBy(c => c.CK3CultureId);

foreach (var grouping in countriesByCulture) {
if (!TryGetValue(grouping.Key!, out var culture)) {
Logger.Warn($"Can't import technology for culture {grouping.Key}: culture not found in CK3 cultures!");
continue;
}

var irInventions = grouping
.SelectMany(c => c.Country.GetActiveInventionIds(inventionsDB))
.ToHashSet();
culture.ImportInnovationsFromImperator(irInventions, innovationMapper);
}
}

private readonly IDictionary<string, string> cultureReplacements = new Dictionary<string, string>(); // replaced culture -> replacing culture

protected readonly PillarCollection PillarCollection;
Expand Down
4 changes: 3 additions & 1 deletion ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ public World(Imperator.World impWorld, Configuration config) {
Logger.Warn($"No base mapping found for I:R culture {irCultureId}!");
}
}

Cultures.ImportTechnology(impWorld.Countries, cultureMapper, provinceMapper, impWorld.InventionsDB);

var traitMapper = new TraitMapper(Path.Combine("configurables", "trait_map.txt"), ModFS);
var traitMapper = new TraitMapper("configurables/trait_map.txt", ModFS);

Logger.Info("Initializing DNA factory...");
var dnaFactory = new DNAFactory(impWorld.ModFS, ModFS);
Expand Down
6 changes: 6 additions & 0 deletions ImperatorToCK3/Imperator/Countries/Country.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using commonItems.Colors;
using ImperatorToCK3.Imperator.Characters;
using ImperatorToCK3.Imperator.Families;
using ImperatorToCK3.Imperator.Inventions;
using ImperatorToCK3.Imperator.Provinces;
using System.Collections.Generic;

Expand Down Expand Up @@ -46,6 +47,7 @@ public string HistoricalTag {
private readonly HashSet<ulong> parsedFamilyIds = [];
public IDictionary<ulong, Family> Families { get; private set; } = new Dictionary<ulong, Family>();
private readonly HashSet<Province> ownedProvinces = [];
private readonly List<bool> inventionBooleans = [];

public CK3.Titles.Title? CK3Title { get; set; }

Expand Down Expand Up @@ -103,4 +105,8 @@ public void LinkOriginCountry(CountryCollection countries) {
OriginCountry = originCountry;
}
}

public IEnumerable<string> GetActiveInventionIds(InventionsDB inventionsDB) {
return inventionsDB.GetActiveInventionIds(inventionBooleans);
}
}
6 changes: 5 additions & 1 deletion ImperatorToCK3/Imperator/Countries/CountryFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using commonItems;
using commonItems;
using commonItems.Colors;
using commonItems.Mods;
using ImperatorToCK3.CommonUtils;
Expand Down Expand Up @@ -86,6 +86,10 @@ static Country() {
parser.RegisterKeyword("family", reader => parsedCountry.parsedFamilyIds.Add(reader.GetULong()));
parser.RegisterKeyword("minor_family", reader => parsedCountry.parsedFamilyIds.Add(reader.GetULong()));
parser.RegisterKeyword("monarch", reader => parsedCountry.monarchId = reader.GetULong());
parser.RegisterKeyword("active_inventions", reader => {
parsedCountry.inventionBooleans.AddRange(reader.GetInts().Select(i => i != 0));
});
parser.RegisterKeyword("mark_invention", ParserHelpers.IgnoreItem);
parser.RegisterKeyword("ruler_term", reader => parsedCountry.RulerTerms.Add(RulerTerm.Parse(reader)));
parser.RegisterRegex(monarchyLawRegexStr, reader => parsedCountry.monarchyLaws.Add(reader.GetString()));
parser.RegisterRegex(republicLawRegexStr, reader => parsedCountry.republicLaws.Add(reader.GetString()));
Expand Down
38 changes: 38 additions & 0 deletions ImperatorToCK3/Imperator/Inventions/InventionsDB.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using commonItems;
using commonItems.Collections;
using commonItems.Mods;
using System.Collections.Generic;
using System.Linq;

namespace ImperatorToCK3.Imperator.Inventions;

public class InventionsDB {
private readonly OrderedSet<string> inventions = [];

public void LoadInventions(ModFilesystem irModFS) {
var inventionsParser = new Parser();
inventionsParser.RegisterKeyword("technology", ParserHelpers.IgnoreItem);
inventionsParser.RegisterKeyword("color", ParserHelpers.IgnoreItem);
inventionsParser.RegisterRegex(CommonRegexes.String, (reader, inventionId) => {
inventions.Add(inventionId);
ParserHelpers.IgnoreItem(reader);
});
inventionsParser.IgnoreAndLogUnregisteredItems();

var inventionGroupsParser = new Parser();
inventionGroupsParser.RegisterRegex(CommonRegexes.String, reader => inventionsParser.ParseStream(reader));
inventionGroupsParser.IgnoreAndLogUnregisteredItems();

Logger.Info("Loading Imperator inventions...");
inventionGroupsParser.ParseGameFolder("common/inventions", irModFS, "txt", recursive: true);
}

public IEnumerable<string> GetActiveInventionIds(IList<bool> booleans) {
// Enumerate over the inventions and return the ones that are active (bool is true).
foreach (var item in inventions.Select((inventionId, i) => new { i, inventionId })) {
if (booleans[item.i]) {
yield return item.inventionId;
}
}
}
}
4 changes: 4 additions & 0 deletions ImperatorToCK3/Imperator/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using ImperatorToCK3.Imperator.Cultures;
using ImperatorToCK3.Imperator.Families;
using ImperatorToCK3.Imperator.Geography;
using ImperatorToCK3.Imperator.Inventions;
using ImperatorToCK3.Imperator.Pops;
using ImperatorToCK3.Imperator.Provinces;
using ImperatorToCK3.Imperator.Religions;
Expand Down Expand Up @@ -56,6 +57,7 @@ public class World : Parser {
public CulturesDB CulturesDB { get; } = new();
public ReligionCollection Religions { get; private set; }
private GenesDB genesDB = new();
public InventionsDB InventionsDB { get; } = new();
public ColorFactory ColorFactory { get; } = new();

private enum SaveType { Invalid, Plaintext, CompressedEncoded }
Expand Down Expand Up @@ -359,6 +361,8 @@ private void LoadModFilesystemDependentData() {
Logger.IncrementProgress();

Defines.LoadDefines(ModFS);

InventionsDB.LoadInventions(ModFS);

Logger.Info("Loading named colors...");
NamedColors.LoadNamedColors("common/named_colors", ModFS);
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/ImperatorToCK3.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
<ItemGroup>
<PackageReference Include="ColorHashSharp" Version="1.0.0" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.6.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.146">
<PackageReference Include="Meziantou.Analyzer" Version="2.0.147">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PGCG.commonItems" Version="11.4.3" />
<PackageReference Include="PGCG.commonItems" Version="12.0.1" />
<PackageReference Include="PGCG.commonItems.SourceGenerators" Version="1.0.5" />
<PackageReference Include="Polly" Version="8.3.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
Expand Down
35 changes: 35 additions & 0 deletions ImperatorToCK3/Mappers/Technology/InnovationBonus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using commonItems;
using System.Collections.Generic;
using System.Linq;

namespace ImperatorToCK3.Mappers.Technology;

public sealed class InnovationBonus { // TODO: add tests
private readonly HashSet<string> imperatorInventions = [];
private string? ck3Innovation;

public InnovationBonus(BufferedReader bonusReader) {
var parser = new Parser();
parser.RegisterKeyword("ir", reader => imperatorInventions.Add(reader.GetString()));
parser.RegisterKeyword("ck3", reader => ck3Innovation = reader.GetString());
parser.IgnoreAndLogUnregisteredItems();
parser.ParseStream(bonusReader);

// A bonus should have at most 3 inventions.
if (imperatorInventions.Count > 3) {
Logger.Warn($"Innovation bonus for {ck3Innovation} has more than 3 inventions: {string.Join(", ", imperatorInventions)}");
}
}

public KeyValuePair<string, ushort>? GetProgress(IEnumerable<string> activeInventions) {
if (ck3Innovation is null) {
return null;
}

// For each matching invention, add 25 to the progress.
int progress = activeInventions
.Where(invention => imperatorInventions.Contains(invention))
.Sum(invention => 25);
return new(ck3Innovation, (ushort)progress);
}
}
23 changes: 23 additions & 0 deletions ImperatorToCK3/Mappers/Technology/InnovationLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using commonItems;

namespace ImperatorToCK3.Mappers.Technology;

public sealed class InnovationLink { // TODO: ADD TESTS
private string? imperatorInvention;
private string? ck3Innovation;

public InnovationLink(BufferedReader linkReader) {
var parser = new Parser();
parser.RegisterKeyword("ir", reader => imperatorInvention = reader.GetString());
parser.RegisterKeyword("ck3", reader => ck3Innovation = reader.GetString());
parser.IgnoreAndLogUnregisteredItems();
parser.ParseStream(linkReader);
}

public string? Match(string irInvention) {
if (imperatorInvention is null) {
return null;
}
return imperatorInvention == irInvention ? ck3Innovation : null;
}
}
50 changes: 50 additions & 0 deletions ImperatorToCK3/Mappers/Technology/InnovationMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using commonItems;
using System.Collections.Generic;

namespace ImperatorToCK3.Mappers.Technology;

public class InnovationMapper {
private readonly List<InnovationLink> innovationLinks = [];
private readonly List<InnovationBonus> innovationBonuses = [];

public void LoadLinksAndBonuses(string configurablePath) {
var parser = new Parser();
parser.RegisterKeyword("link", reader => innovationLinks.Add(new InnovationLink(reader)));
parser.RegisterKeyword("bonus", reader => innovationBonuses.Add(new InnovationBonus(reader)));
parser.IgnoreAndLogUnregisteredItems();
parser.ParseFile(configurablePath);
}

public IList<string> GetInnovations(IEnumerable<string> irInventions) {
var ck3Innovations = new List<string>();
foreach (var irInvention in irInventions) {
foreach (var link in innovationLinks) {
var match = link.Match(irInvention);
if (match is not null) {
ck3Innovations.Add(match);
}
}
}
return ck3Innovations;
}

public IDictionary<string, ushort> GetInnovationProgresses(ICollection<string> irInventions) {
Dictionary<string, ushort> progressesToReturn = [];
foreach (var bonus in innovationBonuses) {
var innovationProgress = bonus.GetProgress(irInventions);
if (!innovationProgress.HasValue) {
continue;
}

if (progressesToReturn.TryGetValue(innovationProgress.Value.Key, out ushort currentValue)) {
// Only the highest progress should be kept.
if (currentValue < innovationProgress.Value.Value) {
progressesToReturn[innovationProgress.Value.Key] = innovationProgress.Value.Value;
}
} else {
progressesToReturn[innovationProgress.Value.Key] = innovationProgress.Value.Value;
}
}
return progressesToReturn;
}
}
Loading

0 comments on commit f9889a6

Please sign in to comment.