Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Technology conversion configurable through configurables/inventions_to_innovations_map.txt #1878

Merged
merged 8 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 / build (self-hosted, linux)

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

View workflow job for this annotation

GitHub Actions / build (macos-latest)

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

View workflow job for this annotation

GitHub Actions / build (self-hosted, windows)

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

View workflow job for this annotation

GitHub Actions / test (self-hosted, windows)

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

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

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

View workflow job for this annotation

GitHub Actions / test (macos-latest)

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
Loading