Skip to content

Convert random country flags #2015

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
8 changes: 2 additions & 6 deletions ImperatorToCK3/CK3/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@
Logger.Info("Loading map data...");
MapData = new MapData(ModFS);

// Load Imperator CoAs to use them for generated CK3 titles
coaMapper = new CoaMapper(impWorld.ModFS);

// Load vanilla CK3 landed titles and their history
LandedTitles.LoadTitles(ModFS);

Expand Down Expand Up @@ -228,7 +225,7 @@
tagTitleMapper,
impWorld.LocDB,
provinceMapper,
coaMapper,
impWorld.CoaMapper,
governmentMapper,
successionLawMapper,
definiteFormMapper,
Expand Down Expand Up @@ -259,7 +256,7 @@
provinceMapper,
definiteFormMapper,
imperatorRegionMapper,
coaMapper,
impWorld.CoaMapper,
countyLevelGovernorships
);

Expand Down Expand Up @@ -550,103 +547,103 @@
return true;
}

private void HandleIcelandAndFaroeIslands(Configuration config) {
Logger.Info("Handling Iceland and Faroe Islands...");
Date bookmarkDate = config.CK3BookmarkDate;
var year = bookmarkDate.Year;

var faiths = Religions.Faiths.ToList();
var titleIdsToHandle = new OrderedSet<string> { "d_iceland", "c_faereyar" };

bool generateHermits = true;
IEnumerable<string> faithCandidates = new OrderedSet<string>();
Queue<string> namePool = new();
const string defaultCultureId = "irish";
string cultureId = defaultCultureId;

switch (year) {
case <= 300:
UsePaganRulers();
break;
case < 874:
faithCandidates = new OrderedSet<string> { "insular_celtic", "catholic", "orthodox" };
var christianFaiths = Religions["christianity_religion"].Faiths;

// If there is at least one Irish Christian county, give it to the Irish Papar.
// If there is at least one Christian county of another Gaelic culture, give it to a character of this Gaelic culture.
var cultureCandidates = new[] { "irish", "gaelic" };
bool provinceFound = false;
foreach (var potentialCultureId in cultureCandidates) {
var cultureProvinces = Provinces.Where(p =>
p.GetCultureId(bookmarkDate) == potentialCultureId);
foreach (var cultureProvince in cultureProvinces) {
var faithId = cultureProvince.GetFaithId(bookmarkDate);
if (faithId is null || !christianFaiths.ContainsKey(faithId)) {
continue;
}
provinceFound = true;
cultureId = potentialCultureId;
faithCandidates = faithCandidates.Prepend(faithId);
break;
}
if (provinceFound) {
break;
}
}
if (!provinceFound) {
// If all the Gaels are pagan but at least one province in Ireland or Scotland is Christian,
// give the handled titles to a generated ruler of the same culture as that Christian province.
var potentialSourceProvinces = Provinces.Where(p =>
ck3RegionMapper.ProvinceIsInRegion(p.Id, "custom_ireland") || ck3RegionMapper.ProvinceIsInRegion(p.Id, "custom_scotland"));
foreach (var potentialSourceProvince in potentialSourceProvinces) {
var faithId = potentialSourceProvince.GetFaithId(bookmarkDate);
if (faithId is null || !christianFaiths.ContainsKey(faithId)) {
continue;
}
provinceFound = true;
cultureId = potentialSourceProvince.GetCultureId(bookmarkDate) ?? defaultCultureId;
faithCandidates = faithCandidates.Prepend(faithId);
break;
}
}
if (!provinceFound) {
// Give up and create a pagan ruler.
UsePaganRulers();
} else {
Logger.Info("Giving Iceland and Faroe Islands to Papar...");
namePool = new Queue<string>(["Canann", "Petair", "Fergus"]);
}
break;
default:
Logger.Info("Keeping Iceland and Faroe Islands as is in history...");
// Let CK3 use rulers from its history.
generateHermits = false;
break;
}

if (generateHermits) {
var faithId = faithCandidates.First(c => faiths.Exists(f => f.Id == c));
foreach (var titleId in titleIdsToHandle) {
if (!LandedTitles.TryGetValue(titleId, out var title)) {
Logger.Warn($"Title {titleId} not found!");
continue;
}

GenerateHermitForTitle(title, namePool, bookmarkDate, faithId, cultureId, config);
}
}

Logger.IncrementProgress();

void UsePaganRulers() {
Logger.Info("Giving Iceland and Faroe Islands to pagan Gaels...");
faithCandidates = new OrderedSet<string> { "gaelic_paganism", "celtic_pagan", "briton_paganism", "pagan" };
cultureId = "gaelic";
// ReSharper disable once StringLiteralTypo
namePool = new Queue<string>(new[] { "A_engus", "Domnall", "Rechtabra" });
}
}

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L550-L646

Complex Method
private void GenerateHermitForTitle(Title title, Queue<string> namePool, Date bookmarkDate, string faithId, string cultureId, Configuration config) {
Logger.Debug($"Generating hermit for {title.Id}...");

Expand Down Expand Up @@ -795,153 +792,152 @@
}
}

private void GenerateFillerHoldersForUnownedLands(CultureCollection cultures, Configuration config) {
Logger.Info("Generating filler holders for unowned lands...");
var date = config.CK3BookmarkDate;
var unheldCounties = LandedTitles
.Where(c => c.Rank == TitleRank.county && c.GetHolderId(date) == "0")
.ToImmutableList();

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.ElementAt(pseudoRandomSeed % maleNames.Count);
} else { // Generate a female if no male name is available.
female = true;
var femaleNames = culture.FemaleNames.ToImmutableList();
name = femaleNames.ElementAt(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(date, "government", "change_government", "tribal_government");
Characters.AddOrReplace(holder);

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("tribal_government", date);
duchyIdToHolderDict[duchy.Id] = holder;
} else {
county.SetGovernment("tribal_government", date);
}
}
}

private readonly CoaMapper coaMapper;
private readonly DeathReasonMapper deathReasonMapper = new();
private readonly DefiniteFormMapper definiteFormMapper = new(Path.Combine("configurables", "definite_form_names.txt"));
private readonly NicknameMapper nicknameMapper = new(Path.Combine("configurables", "nickname_map.txt"));
private readonly ProvinceMapper provinceMapper = new();
private readonly SuccessionLawMapper successionLawMapper = new(Path.Combine("configurables", "succession_law_map.txt"));
private readonly TagTitleMapper tagTitleMapper = new(
tagTitleMappingsPath: Path.Combine("configurables", "title_map.txt"),
governorshipTitleMappingsPath: Path.Combine("configurables", "governorMappings.txt"),
rankMappingsPath: "configurables/country_rank_map.txt"
);
private readonly UnitTypeMapper unitTypeMapper = new("configurables/unit_types_map.txt");
private readonly CK3RegionMapper ck3RegionMapper;
private readonly ImperatorRegionMapper imperatorRegionMapper;
private readonly WarMapper warMapper = new("configurables/wargoal_mappings.txt");

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

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/World.cs#L795-L943

Complex Method
Expand Down
221 changes: 215 additions & 6 deletions ImperatorToCK3/Imperator/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@
using ImperatorToCK3.Imperator.Provinces;
using ImperatorToCK3.Imperator.Religions;
using ImperatorToCK3.Imperator.States;
using ImperatorToCK3.Mappers.CoA;
using ImperatorToCK3.Mappers.Region;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Mods = System.Collections.Generic.List<commonItems.Mods.Mod>;
using Parser = commonItems.Parser;

namespace ImperatorToCK3.Imperator;

public class World {
public Date EndDate { get; private set; } = new Date("727.2.17", AUC: true);
private readonly IList<string> incomingModPaths = []; // List of all mods used in the save.
public ModFilesystem ModFS { get; private set; }
private readonly SortedSet<string> dlcs = new();
public IReadOnlySet<string> GlobalFlags { get; private set; } = ImmutableHashSet<string>.Empty;
Expand All @@ -45,6 +51,7 @@ public class World {
private PopCollection pops = new();
public ProvinceCollection Provinces { get; } = new();
public CountryCollection Countries { get; } = new();
public CoaMapper CoaMapper { get; private set; } = new();
public MapData MapData { get; private set; }
public AreaCollection Areas { get; } = new();
public ImperatorRegionMapper ImperatorRegionMapper { get; private set; }
Expand All @@ -61,6 +68,7 @@ public class World {

private enum SaveType { Invalid, Plaintext, CompressedEncoded }
private SaveType saveType = SaveType.Invalid;
private string metaPlayerName = string.Empty;

protected World(Configuration config) {
ModFS = new ModFilesystem(Path.Combine(config.ImperatorPath, "game"), Array.Empty<Mod>());
Expand All @@ -70,6 +78,198 @@ protected World(Configuration config) {
ImperatorRegionMapper = new ImperatorRegionMapper(Areas, MapData);
}

private static void OutputGuiContainer(ModFilesystem modFS, IEnumerable<string> tagsNeedingFlags, Configuration config) {
Logger.Debug("Modifying gui for exporting CoAs...");

const string relativeTopBarGuiPath = "gui/ingame_topbar.gui";
var topBarGuiPath = modFS.GetActualFileLocation(relativeTopBarGuiPath);
if (topBarGuiPath is null) {
Logger.Warn($"{relativeTopBarGuiPath} not found, can't write CoA export commands!");
return;
}

var guiTextBuilder = new StringBuilder();
guiTextBuilder.AppendLine("\tstate = {");
guiTextBuilder.AppendLine("\t\tname = _show");
string commandsString = string.Join(';', tagsNeedingFlags.Select(tag => $"coat_of_arms {tag}"));
commandsString += ";dumpdatatypes"; // This will let us know when the commands finished executing.
guiTextBuilder.AppendLine($"\t\ton_start=\"[ExecuteConsoleCommandsForced('{commandsString}')]\"");
guiTextBuilder.AppendLine("\t}");

List<string> lines = File.ReadAllLines(topBarGuiPath).ToList();
int index = lines.FindIndex(line => line.Contains("name = \"ingame_topbar\""));
if (index != -1) {
lines.Insert(index + 1, guiTextBuilder.ToString());
}

var topBarOutputPath = Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod", relativeTopBarGuiPath);
Logger.Debug($"Writing modified GUI to \"{topBarOutputPath}\"...");
var topBarOutputDir = Path.GetDirectoryName(topBarOutputPath);
if (topBarOutputDir is not null) {
Directory.CreateDirectory(topBarOutputDir);
}
File.WriteAllLines(topBarOutputPath, lines);

// Create a .mod file for the temporary mod.
Logger.Debug("Creating temporary mod file...");
string modFileContents =
"""
name = "IRToCK3 CoA export mod"
path = "mod/coa_export_mod"
""";
File.WriteAllText(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod/descriptor.mod"), modFileContents);

var absoluteModPath = Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod").Replace('\\', '/');
modFileContents = modFileContents.Replace("path = \"mod/coa_export_mod\"", $"path = \"{absoluteModPath}\"");
File.WriteAllText(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod.mod"), modFileContents);
}

private void OutputContinueGameJson(Configuration config) {
// Set the current save to be used when launching the game with the continuelastsave option.
Logger.Debug("Modifying continue_game.json...");
File.WriteAllText(Path.Join(config.ImperatorDocPath, "continue_game.json"),
contents: $$"""
{
"title": "{{Path.GetFileNameWithoutExtension(config.SaveGamePath)}}",
"desc": "Playing as {{metaPlayerName}} - {{EndDate}} AD",
"date": "{{DateTime.Now:yyyy-MM-dd HH:mm:ss}}"
}
""");
}

private void OutputDlcLoadJson(Configuration config) {
Logger.Debug("Outputting dlc_load.json...");
var dlcLoadBuilder = new StringBuilder();
dlcLoadBuilder.AppendLine("{");
dlcLoadBuilder.Append(@"""enabled_mods"": [");
dlcLoadBuilder.AppendJoin(", ", incomingModPaths.Select(modPath => $"\"{modPath}\""));
dlcLoadBuilder.AppendLine(",");
dlcLoadBuilder.AppendLine("\"mod/coa_export_mod.mod\"");
dlcLoadBuilder.AppendLine("],");
dlcLoadBuilder.AppendLine(@"""disabled_dlcs"":[]");
dlcLoadBuilder.AppendLine("}");
File.WriteAllText(Path.Join(config.ImperatorDocPath, "dlc_load.json"), dlcLoadBuilder.ToString());
}

private void LaunchImperatorToExportCountryFlags(Configuration config) {
OutputContinueGameJson(config);
OutputDlcLoadJson(config);

string imperatorBinaryName = OperatingSystem.IsWindows() ? "imperator.exe" : "imperator";
var imperatorBinaryPath = Path.Combine(config.ImperatorPath, "binaries", imperatorBinaryName);
if (!File.Exists(imperatorBinaryPath)) {
Logger.Error("Imperator binary not found! Aborting!");
}

string dataTypesLogPath = Path.Combine(config.ImperatorDocPath, "logs/data_types.log");
if (File.Exists(dataTypesLogPath)) {
File.Delete(dataTypesLogPath);
}

Logger.Info("Launching Imperator to extract coats of arms...");

var processStartInfo = new ProcessStartInfo {
FileName = imperatorBinaryPath,
Arguments = "-continuelastsave -debug_mode",
CreateNoWindow = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Hidden
};
var imperatorProcess = Process.Start(processStartInfo);
if (imperatorProcess is null) {
Logger.Warn("Failed to start Imperator process! Aborting!");
return;
}

imperatorProcess.Exited += HandleImperatorProcessExit(config, imperatorProcess);

// Make sure that if converter is closed, Imperator is closed as well.
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
if (!imperatorProcess.HasExited) {
imperatorProcess.Kill();
}
};

// Wait until data_types.log exists (it will be created by the dumpdatatypes command).
var stopwatch = new Stopwatch();
stopwatch.Start();
while (!imperatorProcess.HasExited && !File.Exists(dataTypesLogPath)) {
if (stopwatch.Elapsed > TimeSpan.FromMinutes(5)) {
Logger.Warn("Imperator process took too long to execute console commands! Aborting!");
imperatorProcess.Kill();
break;
}

if (imperatorProcess.StandardOutput.ReadLine()?.Contains("Updating cached data done") == true) {
Logger.Debug("Imperator finished loading. Waiting for console commands to execute...");
}

Thread.Sleep(100);
}

if (!imperatorProcess.HasExited) {
Logger.Debug("Killing Imperator process...");
imperatorProcess.Kill();
}
}

private static EventHandler HandleImperatorProcessExit(Configuration config, Process imperatorProcess) {
return (_, _) => {
Logger.Debug($"Imperator process exited with code {imperatorProcess.ExitCode}. Removing temporary mod files...");
try {
File.Delete(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod.mod"));
Directory.Delete(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod"), recursive: true);
} catch (Exception e) {
Logger.Warn($"Failed to remove temporary mod files: {e.Message}");
}
};
}

private void ReadCoatsOfArmsFromGameLog(string imperatorDocPath) {
Logger.Info("Reading CoAs from game log...");
string inputFilePath = Path.Combine(imperatorDocPath, "logs/game.log");

using var saveStream = File.Open(inputFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(saveStream);
string content = reader.ReadToEnd();

// Remove everything prior to the first line contatining "Coat of arms:"
int startIndex = content.IndexOf("Coat of arms:", StringComparison.Ordinal);
if (startIndex == -1) {
Logger.Warn("No CoAs found in game log.");
return;
}
content = content.Substring(startIndex);

string pattern = @"^\S+=\s*\{[\s\S]*?^\}";
MatchCollection matches = Regex.Matches(content, pattern, RegexOptions.Multiline);

CoaMapper.ParseCoAs(matches.Select(match => match.Value));
}

private void ExtractDynamicCoatsOfArms(Configuration config) {
var countryFlags = Countries.Select(country => country.Flag).ToList();
var missingFlags = CoaMapper.GetAllMissingFlagKeys(countryFlags);
if (missingFlags.Count == 0) {
return;
}

Logger.Debug("Missing country flag definitions: " + string.Join(", ", missingFlags));

var tagsWithMissingFlags = Countries
.Where(country => missingFlags.Contains(country.Flag))
.Select(country => country.Tag);

OutputGuiContainer(ModFS, tagsWithMissingFlags, config);
LaunchImperatorToExportCountryFlags(config);
ReadCoatsOfArmsFromGameLog(config.ImperatorDocPath);

var missingFlagsAfterExtraction = CoaMapper.GetAllMissingFlagKeys(countryFlags);
if (missingFlagsAfterExtraction.Count > 0) {
Logger.Warn("Failed to export the following country flags: " + string.Join(", ", missingFlagsAfterExtraction));
}
}

public World(Configuration config, ConverterVersion converterVersion) {
Logger.Info("*** Hello Imperator, Roma Invicta! ***");

Expand All @@ -94,6 +294,8 @@ public World(Configuration config, ConverterVersion converterVersion) {
}

Logger.Info("*** Building World ***");

ExtractDynamicCoatsOfArms(config);

// Link all the intertwining references
Logger.Info("Linking Characters with Families...");
Expand Down Expand Up @@ -142,7 +344,7 @@ private void ParseSave(Configuration config, ConverterVersion converterVersion)
parser.RegisterKeyword("diplomacy", LoadDiplomacy);
parser.RegisterKeyword("jobs", LoadJobs);
parser.RegisterKeyword("deity_manager", reader => Religions.LoadHolySiteDatabase(reader));
parser.RegisterKeyword("meta_player_name", ParserHelpers.IgnoreItem);
parser.RegisterKeyword("meta_player_name", reader => metaPlayerName = reader.GetString());
parser.RegisterKeyword("speed", ParserHelpers.IgnoreItem);
parser.RegisterKeyword("random_seed", ParserHelpers.IgnoreItem);
parser.RegisterKeyword("tutorial_disable", ParserHelpers.IgnoreItem);
Expand All @@ -157,12 +359,18 @@ private void ParseSave(Configuration config, ConverterVersion converterVersion)
Logger.IncrementProgress();
}

private static Mods DetectUsedMods(BufferedReader reader) {
private Mods DetectUsedMods(BufferedReader reader) {
Logger.Info("Detecting used mods...");
var modsList = reader.GetStrings();
Logger.Info($"Save game claims {modsList.Count} mods used:");
Mods incomingMods = new();
foreach (var modPath in modsList) {
foreach (var modPath in reader.GetStrings()) {
incomingModPaths.Add(modPath);
}
if (incomingModPaths.Count == 0) {
Logger.Warn("Save game claims no mods used.");
} else {
Logger.Info($"Save game claims {incomingModPaths.Count} mods used:");
}
Mods incomingMods = [];
foreach (var modPath in incomingModPaths) {
Logger.Info($"Used mod: {modPath}");
incomingMods.Add(new Mod(string.Empty, modPath));
}
Expand Down Expand Up @@ -416,6 +624,7 @@ private void LoadModFilesystemDependentData() {
ImperatorRegionMapper.LoadRegions(ModFS, ColorFactory);

Country.LoadGovernments(ModFS);
CoaMapper = new CoaMapper(ModFS);

CulturesDB.Load(ModFS);

Expand Down
25 changes: 22 additions & 3 deletions ImperatorToCK3/Mappers/CoA/CoaMapper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using commonItems;
using commonItems.Mods;
using System.Collections.Generic;
using System.Linq;

namespace ImperatorToCK3.Mappers.CoA;

Expand All @@ -22,9 +23,27 @@ private void RegisterKeys(Parser parser) {
parser.RegisterRegex(CommonRegexes.Catchall, (reader, flagName) => coasMap[flagName] = reader.GetStringOfItem().ToString());
}

public string? GetCoaForFlagName(string impFlagName) {
bool contains = coasMap.TryGetValue(impFlagName, out string? value);
return contains ? value : null;
public void ParseCoAs(IEnumerable<string> coaDefinitionStrings) {
var parser = new Parser();
RegisterKeys(parser);
foreach (var coaDefinitionString in coaDefinitionStrings) {
parser.ParseStream(new BufferedReader(coaDefinitionString));
}
}

public string? GetCoaForFlagName(string irFlagName) {
if (!coasMap.TryGetValue(irFlagName, out string? value)) {
Logger.Warn($"No CoA defined for flag name {irFlagName}.");
return null;
}

return value;
}

/// For a given collection of flag names, returns ones that don't have a defined CoA.
public ISet<string> GetAllMissingFlagKeys(IEnumerable<string> flagKeys) {
var existingFlagKeys = coasMap.Keys.ToHashSet();
return flagKeys.Where(flagKey => !existingFlagKeys.Contains(flagKey)).ToHashSet();
}

private readonly Dictionary<string, string> coasMap = new();
Expand Down
2 changes: 2 additions & 0 deletions ImperatorToCK3/Outputter/MenAtArmsOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ private static void OutputGuiContainer(string outputModName, ModFilesystem modFS
foreach (var character in charactersWithMaa) {
foreach (var (maaType, stacks) in character.MenAtArmsStacksPerType) {
for (int i = 0; i < stacks; ++i) {
// TODO: Use ExecuteConsoleCommands instead of using ExecuteConsoleCommand in a loop
// TODO: use on_finish instead of on_start, on_start may execute twice according to a CK3 mod coop
sb.AppendLine(
"\t\tstate = { " +
$"name=state{state++} " +
Expand Down
Loading