Skip to content

Fix duplicate holy sites localization being outputted #2145

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 1 commit into from
Sep 7, 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
193 changes: 56 additions & 137 deletions ImperatorToCK3/CK3/CK3LocDB.cs
Original file line number Diff line number Diff line change
@@ -1,94 +1,85 @@
using commonItems;
using commonItems.Collections;
using commonItems.Localization;
using commonItems.Mods;
using System.Collections;
using ImperatorToCK3.CK3.Localization;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace ImperatorToCK3.CK3;

public class CK3LocDB : IEnumerable<LocBlock> {
private LocDB ModFSLocDB { get; } = new LocDB(ConverterGlobals.PrimaryLanguage, ConverterGlobals.SecondaryLanguages);
private LocDB ConverterGeneratedLocDB { get; } = new LocDB(ConverterGlobals.PrimaryLanguage, ConverterGlobals.SecondaryLanguages);
private LocDB OptionalConverterLocDB { get; } = new LocDB(ConverterGlobals.PrimaryLanguage, ConverterGlobals.SecondaryLanguages);
public class CK3LocDB : IdObjectCollection<string, CK3LocBlock> {
public CK3LocDB() { } // For unit tests.

protected CK3LocDB() { } // Only for inheritance

public CK3LocDB(ModFilesystem ck3ModFS) {
// Read loc from CK3 and selected CK3 mods.
ModFSLocDB.ScrapeLocalizations(ck3ModFS);
var modFSLocDB = new LocDB(ConverterGlobals.PrimaryLanguage, ConverterGlobals.SecondaryLanguages);
modFSLocDB.ScrapeLocalizations(ck3ModFS);
ImportLocFromLocDB(modFSLocDB);

// Read loc from ImperatorToCK3 congifurables.
// It will only be outputted for keys localized in neither ModFSLocDB nor ConverterGeneratedLocDB.
LoadOptionalLoc();
}

private void ImportLocFromLocDB(LocDB locDB) {
foreach (var locBlock in locDB) {
var ck3LocBlock = GetOrCreateLocBlock(locBlock.Id);
foreach (var (language, loc) in locBlock) {
if (loc is null) {
continue;
}
ck3LocBlock.AddModFSLoc(language, loc);
}
}
}

private void LoadOptionalLoc() {
const string optionalLocDir = "configurables/localization";
if (!Directory.Exists(optionalLocDir)) {
Logger.Warn("Optional loc directory not found, skipping optional loc loading.");
return;
}

var optionalConverterLocDB = new LocDB(ConverterGlobals.PrimaryLanguage, ConverterGlobals.SecondaryLanguages);
var optionalLocFilePaths = Directory.GetFiles(optionalLocDir, "*.yml", SearchOption.AllDirectories);
foreach (var outputtedLocFilePath in optionalLocFilePaths) {
OptionalConverterLocDB.ScrapeFile(outputtedLocFilePath);
optionalConverterLocDB.ScrapeFile(outputtedLocFilePath);
}

foreach (var locBlock in optionalConverterLocDB) {
// Only add loc for the languages that are not already in the CK3LocDB.
var ck3LocBlock = GetOrCreateLocBlock(locBlock.Id);
foreach (var (language, loc) in locBlock) {
if (loc is null) {
continue;
}
if (!ck3LocBlock.HasLocForLanguage(language)) {
ck3LocBlock.AddOptionalLoc(language, loc);
}
}
}
}

private readonly object insertionLock = new();

public LocBlock AddLocBlock(string id) {
public CK3LocBlock GetOrCreateLocBlock(string id) {
lock (insertionLock) {
return ConverterGeneratedLocDB.AddLocBlock(id);
}
}

public bool ContainsKey(string key) {
if (ModFSLocDB.ContainsKey(key)) {
return true;
}
if (ConverterGeneratedLocDB.ContainsKey(key)) {
return true;
}
if (OptionalConverterLocDB.ContainsKey(key)) {
return true;
}
return false;
}

public bool TryGetValue(string key, [MaybeNullWhen(false)] out LocBlock locBlock) { // TODO: return readonly locblock instead
bool found = false;
locBlock = null;

// TODO: add unit test for combining loc from all the sources into one locblock

if (OptionalConverterLocDB.TryGetValue(key, out var optionalLocBlock)) {
found = true;
locBlock = optionalLocBlock;
}
if (ConverterGeneratedLocDB.TryGetValue(key, out var converterGeneratedLocDBLocBlock)) {
found = true;
if (locBlock is null) {
locBlock = converterGeneratedLocDBLocBlock;
} else {
locBlock.CopyFrom(converterGeneratedLocDBLocBlock);
}
}
if (ModFSLocDB.TryGetValue(key, out var modFSLocBlock)) {
found = true;
if (locBlock is null) {
locBlock = modFSLocBlock;
} else {
locBlock.CopyFrom(modFSLocBlock);
if (TryGetValue(id, out var locBlock)) {
return locBlock;
}

// Create new loc block.
locBlock = new CK3LocBlock(id, ConverterGlobals.PrimaryLanguage);
Add(locBlock);
return locBlock;
}

return found;
}

public LocBlock? GetLocBlockForKey(string key) {
// TODO: add unit test for combining loc from all the sources into one locblock

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / build (macos-14)

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / build (self-hosted, windows)

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / test (macos-14)

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / build (self-hosted, linux)

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / test_and_check_coverage

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)

Check warning on line 79 in ImperatorToCK3/CK3/CK3LocDB.cs

View workflow job for this annotation

GitHub Actions / test (self-hosted, windows)

TODO add unit test for combining loc from all the sources into one locblock (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md)


public CK3LocBlock? GetLocBlockForKey(string key) {
if (TryGetValue(key, out var locBlock)) {
return locBlock;
}
Expand All @@ -97,106 +88,35 @@
}

public bool HasKeyLocForLanguage(string key, string language) {
if (ModFSLocDB.ContainsKey(key) && ModFSLocDB[key].HasLocForLanguage(language)) {
return true;
}
if (ConverterGeneratedLocDB.ContainsKey(key) && ConverterGeneratedLocDB[key].HasLocForLanguage(language)) {
return true;
}
if (OptionalConverterLocDB.ContainsKey(key) && OptionalConverterLocDB[key].HasLocForLanguage(language)) {
return true;
if (TryGetValue(key, out var locBlock)) {
return locBlock.HasLocForLanguage(language);
}

return false;
}

public void AddLocForLanguage(string key, string language, string loc) {
lock (insertionLock) {
ConverterGeneratedLocDB.AddLocForKeyAndLanguage(key, language, loc);
var locBlock = GetOrCreateLocBlock(key);
locBlock[language] = loc;
}
}

public string? GetYmlLocLineForLanguage(string key, string language) {
if (ConverterGeneratedLocDB.TryGetValue(key, out var locBlock) && locBlock.HasLocForLanguage(language)) {
return locBlock.GetYmlLocLineForLanguage(language);
}
if (ModFSLocDB.TryGetValue(key, out locBlock) && locBlock.HasLocForLanguage(language)) {
return locBlock.GetYmlLocLineForLanguage(language);
}
if (OptionalConverterLocDB.TryGetValue(key, out locBlock) && locBlock.HasLocForLanguage(language)) {
if (TryGetValue(key, out var locBlock) && locBlock.HasLocForLanguage(language)) {
return locBlock.GetYmlLocLineForLanguage(language);
}

return null;
}

IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}

public IEnumerator<LocBlock> GetEnumerator() {
var alreadyOutputtedLocKeys = new HashSet<string>();

foreach (var locBlock in ModFSLocDB) {
yield return locBlock;
alreadyOutputtedLocKeys.Add(locBlock.Id);
}

foreach (var locBlock in ConverterGeneratedLocDB) {
if (alreadyOutputtedLocKeys.Contains(locBlock.Id)) {
continue;
}

yield return locBlock;
alreadyOutputtedLocKeys.Add(locBlock.Id);
}

foreach (var locBlock in OptionalConverterLocDB) {
if (alreadyOutputtedLocKeys.Contains(locBlock.Id)) {
continue;
}

yield return locBlock;
alreadyOutputtedLocKeys.Add(locBlock.Id);
}
}

private HashSet<string> GetAlreadyOutputtedLocKeysForLanguage(string language) {
var keysPerLanguage = new HashSet<string>();

foreach (var locBlock in ModFSLocDB) {
if (locBlock.HasLocForLanguage(language)) {
keysPerLanguage.Add(locBlock.Id);
}
}

return keysPerLanguage;
}

public List<string> GetLocLinesToOutputForLanguage(string language) {
var locLinesToOutput = new List<string>();

var alreadyWrittenLocForLanguage = GetAlreadyOutputtedLocKeysForLanguage(language);

foreach (var locBlock in ConverterGeneratedLocDB) {
if (!locBlock.HasLocForLanguage(language)) {
continue;
}

var loc = locBlock[language];
if (loc is null) {
continue;
}

locLinesToOutput.Add(locBlock.GetYmlLocLineForLanguage(language));
alreadyWrittenLocForLanguage.Add(locBlock.Id);
}

foreach (var locBlock in OptionalConverterLocDB) {
if (alreadyWrittenLocForLanguage.Contains(locBlock.Id)) {
continue;
}

if (!locBlock.HasLocForLanguage(language)) {
foreach (var locBlock in this) {
if (locBlock.GetLocTypeForLanguage(language) is null or CK3LocType.CK3ModFS) {
// If there's no loc for the language, the returned loc type is null.
// CK3ModFS locs are already present in the CK3/mod/blankMod files, we don't need to output them.
continue;
}

Expand All @@ -206,7 +126,6 @@
}

locLinesToOutput.Add(locBlock.GetYmlLocLineForLanguage(language));
alreadyWrittenLocForLanguage.Add(locBlock.Id);
}

return locLinesToOutput;
Expand Down
13 changes: 7 additions & 6 deletions ImperatorToCK3/CK3/Characters/Character.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using commonItems.Collections;
using commonItems.Localization;
using ImperatorToCK3.CK3.Armies;
using ImperatorToCK3.CK3.Localization;
using ImperatorToCK3.CommonUtils;
using ImperatorToCK3.CommonUtils.Map;
using ImperatorToCK3.Exceptions;
Expand Down Expand Up @@ -279,7 +280,7 @@
SetName(name, null);
if (!string.IsNullOrEmpty(name)) {
var impNameLoc = irLocDB.GetLocBlockForKey(name);
LocBlock ck3NameLoc = ck3LocDB.AddLocBlock(name);
CK3LocBlock ck3NameLoc = ck3LocDB.GetOrCreateLocBlock(name);
if (impNameLoc is not null) {
ck3NameLoc.CopyFrom(impNameLoc);
} else { // fallback: use unlocalized name as displayed name
Expand Down Expand Up @@ -330,177 +331,177 @@
}
}

public Character(
Imperator.Characters.Character impCharacter,
CharacterCollection characters,
ReligionMapper religionMapper,
CultureMapper cultureMapper,
TraitMapper traitMapper,
NicknameMapper nicknameMapper,
LocDB irLocDB,
CK3LocDB ck3LocDB,
MapData irMapData,
ProvinceMapper provinceMapper, // used to determine ck3 province for religion mapper
DeathReasonMapper deathReasonMapper,
DNAFactory dnaFactory,
Date dateOnConversion,
Configuration config,
ISet<string> unlocalizedImperatorNames
) {
this.characters = characters;

ImperatorCharacter = impCharacter;
ImperatorCharacter.CK3Character = this;
Id = "imperator" + ImperatorCharacter.Id;
FromImperator = true;

if (!string.IsNullOrEmpty(ImperatorCharacter.CustomName)) {
var loc = ImperatorCharacter.CustomName;
var locKey = CommonFunctions.NormalizeUTF8Path(loc.FoldToASCII().Replace(' ', '_'));
var name = $"IRTOCK3_CUSTOM_NAME_{locKey}";
SetName(name, null);

var ck3NameLocBlock = ck3LocDB.AddLocBlock(name);
var ck3NameLocBlock = ck3LocDB.GetOrCreateLocBlock(name);
foreach (var language in ConverterGlobals.SupportedLanguages) {
ck3NameLocBlock[language] = loc;
}
} else {
var nameLoc = ImperatorCharacter.Name;
var name = nameLoc.Replace(' ', '_');
SetName(name, null);
if (!string.IsNullOrEmpty(name)) {
var ck3NameLocBlock = ck3LocDB.AddLocBlock(name);
var ck3NameLocBlock = ck3LocDB.GetOrCreateLocBlock(name);
var matchedLocBlock = irLocDB.GetLocBlockForKey(name);
if (matchedLocBlock is not null) {
ck3NameLocBlock.CopyFrom(matchedLocBlock);
} else { // fallback: use unlocalized name as displayed name
unlocalizedImperatorNames.Add(name);
ck3NameLocBlock[ConverterGlobals.PrimaryLanguage] = nameLoc;
}
}
}

Female = ImperatorCharacter.Female;

if (ImperatorCharacter.PortraitData is not null) {
DNA = dnaFactory.GenerateDNA(ImperatorCharacter, ImperatorCharacter.PortraitData);
}

// Determine valid (not dropped in province mappings) "source I:R province" and "source CK3 province"
// to be used by religion mapper. Don't give up without a fight.
ulong? irProvinceId = ImperatorCharacter.GetSourceLandProvince(irMapData);

var impProvForProvinceMapper = irProvinceId;
if ((!impProvForProvinceMapper.HasValue || provinceMapper.GetCK3ProvinceNumbers(impProvForProvinceMapper.Value).Count == 0) && ImperatorCharacter.Father is not null) {
impProvForProvinceMapper = ImperatorCharacter.Father.ProvinceId;
}
if ((!impProvForProvinceMapper.HasValue || provinceMapper.GetCK3ProvinceNumbers(impProvForProvinceMapper.Value).Count == 0) && ImperatorCharacter.Mother is not null) {
impProvForProvinceMapper = ImperatorCharacter.Mother.ProvinceId;
}
if ((!impProvForProvinceMapper.HasValue || provinceMapper.GetCK3ProvinceNumbers(impProvForProvinceMapper.Value).Count == 0) && ImperatorCharacter.Spouses.Count > 0) {
var firstSpouse = ImperatorCharacter.Spouses.First().Value;
impProvForProvinceMapper = firstSpouse.ProvinceId;
}

var ck3ProvinceNumbers = impProvForProvinceMapper.HasValue ? provinceMapper.GetCK3ProvinceNumbers(impProvForProvinceMapper.Value) : [];
ulong? ck3ProvinceId = ck3ProvinceNumbers.Count > 0 ? ck3ProvinceNumbers[0] : null;

var cultureMatch = cultureMapper.Match(
ImperatorCharacter.Culture,
ck3ProvinceId,
irProvinceId,
ImperatorCharacter.Country?.HistoricalTag
);
if (cultureMatch is null) {
Logger.Warn($"Could not determine CK3 culture for Imperator character {ImperatorCharacter.Id}" +
$" with culture {ImperatorCharacter.Culture}!");
} else {
SetCultureId(cultureMatch, null);
}

var faithMatch = religionMapper.Match(
ImperatorCharacter.Religion,
GetCultureId(dateOnConversion),
ck3ProvinceId,
irProvinceId,
ImperatorCharacter.HomeCountry?.HistoricalTag,
config
);
if (faithMatch is not null) {
SetFaithId(faithMatch, null);
}

// Determine character attributes.
History.AddFieldValue(null, "diplomacy", "diplomacy", ImperatorCharacter.Attributes.Charisma);
History.AddFieldValue(null, "martial", "martial", ImperatorCharacter.Attributes.Martial);
History.AddFieldValue(null, "stewardship", "stewardship", ImperatorCharacter.Attributes.Finesse);
var intrigue = (ImperatorCharacter.Attributes.Finesse + ImperatorCharacter.Attributes.Charisma) / 2;
History.AddFieldValue(null, "intrigue", "intrigue", intrigue);
History.AddFieldValue(null, "learning", "learning", ImperatorCharacter.Attributes.Zeal);

if (impCharacter.Fertility.HasValue) {
History.AddFieldValue(null, "fertility", "fertility", impCharacter.Fertility.Value);
}

if (impCharacter.Health is not null) {
// In I:R, health is a value between 0 and 100, with 100 being the best.
// In CK3, 0 means near death, ≥ 7 means excellent health.
// https://imperator.paradoxwikis.com/Characters#Secondary
// https://ck3.paradoxwikis.com/Attributes#Health
var ck3Health = impCharacter.Health.Value / 10;
History.AddFieldValue(null, "health", "health", ck3Health);
}

foreach (var traitId in traitMapper.GetCK3TraitsForImperatorTraits(ImperatorCharacter.Traits)) {
AddBaseTrait(traitId);
}

BirthDate = ImperatorCharacter.BirthDate;
DeathDate = ImperatorCharacter.DeathDate;
var impDeathReason = ImperatorCharacter.DeathReason;
if (impDeathReason is not null) {
DeathReason = deathReasonMapper.GetCK3ReasonForImperatorReason(impDeathReason);
}

var nicknameMatch = nicknameMapper.GetCK3NicknameForImperatorNickname(ImperatorCharacter.Nickname);
if (nicknameMatch is not null) {
SetNickname(nicknameMatch, dateOnConversion);
}

if (ImperatorCharacter.Wealth != 0) {
Gold = ImperatorCharacter.Wealth * config.ImperatorCurrencyRate;
}

// If character is imprisoned, set jailor.
SetJailor();
SetEmployerFromImperator();

void SetJailor() {
if (ImperatorCharacter.PrisonerHome is null) {
return;
}

var prisonCountry = ImperatorCharacter.Country;
if (prisonCountry is null) {
Logger.Warn($"Imperator character {ImperatorCharacter.Id} is imprisoned but has no country!");
} else if (prisonCountry.CK3Title is null) {
Logger.Debug($"Imperator character {ImperatorCharacter.Id}'s prison country does not exist in CK3!");
} else {
jailorId = prisonCountry.CK3Title.GetHolderId(dateOnConversion);
}
}

void SetEmployerFromImperator() {
var prisonerHome = ImperatorCharacter.PrisonerHome;
var homeCountry = ImperatorCharacter.HomeCountry;
if (prisonerHome?.CK3Title is not null) { // is imprisoned
SetEmployerId(prisonerHome.CK3Title.GetHolderId(dateOnConversion), null);
} else if (homeCountry?.CK3Title is not null) {
SetEmployerId(homeCountry.CK3Title.GetHolderId(dateOnConversion), null);
}
}
}

Check notice on line 504 in ImperatorToCK3/CK3/Characters/Character.cs

View check run for this annotation

codefactor.io / CodeFactor

ImperatorToCK3/CK3/Characters/Character.cs#L334-L504

Complex Method
public void SetCultureId(string cultureId, Date? date) {
History.AddFieldValue(date, "culture", "culture", cultureId);
}
Expand Down Expand Up @@ -694,7 +695,7 @@
CK3LocDB ck3LocDB
) {
var locKey = $"IRToCK3_character_{Id}";
var locBlock = ck3LocDB.AddLocBlock(locKey);
var locBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
locBlock[ConverterGlobals.PrimaryLanguage] = $"[GetPlayer.MakeScope.Var('IRToCK3_character_{Id}').Char.GetID]";

var menPerUnitType = new Dictionary<string, int>();
Expand All @@ -714,7 +715,7 @@
menAtArmsTypes.Add(dedicatedType);
MenAtArmsStacksPerType[dedicatedType.Id] = 1;

var maaTypeLocBlock = ck3LocDB.AddLocBlock(dedicatedType.Id);
var maaTypeLocBlock = ck3LocDB.GetOrCreateLocBlock(dedicatedType.Id);
maaTypeLocBlock[ConverterGlobals.PrimaryLanguage] = $"${baseType.Id}$";
}

Expand Down Expand Up @@ -748,7 +749,7 @@
if (unit.LocalizedName is not null) {
var locKey = unit.LocalizedName.Id;
sb.AppendLine($"\t\t\tname={locKey}");
var unitLocBlock = ck3LocDB.AddLocBlock(locKey);
var unitLocBlock = ck3LocDB.GetOrCreateLocBlock(locKey);
unitLocBlock.CopyFrom(unit.LocalizedName);
}

Expand Down
2 changes: 1 addition & 1 deletion ImperatorToCK3/CK3/Dynasties/Dynasty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private void SetCultureFromImperator(Family irFamily, IReadOnlyList<Character> i

private void SetLocFromImperatorFamilyName(string irFamilyLocKey, LocDB irLocDB, CK3LocDB ck3LocDB) {
var irFamilyLoc = irLocDB.GetLocBlockForKey(irFamilyLocKey);
var ck3NameLoc = ck3LocDB.AddLocBlock(Name);
var ck3NameLoc = ck3LocDB.GetOrCreateLocBlock(Name);
if (irFamilyLoc is not null) {
ck3NameLoc.CopyFrom(irFamilyLoc);
ck3NameLoc.ModifyForEveryLanguage(irFamilyLoc, (orig, other, lang) => {
Expand Down
Loading
Loading