Skip to content

Commit ac6772f

Browse files
Countless fixes to TFE conversion but apparently not enough to fix the crashes (#2423) #patch
#2202 --------- Co-authored-by: codefactor-io <[email protected]>
1 parent 1d8bb35 commit ac6772f

36 files changed

+42585
-7814
lines changed

ImperatorToCK3.UnitTests/CommonUtils/HistoryTests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,35 @@ public void NullIsReturnedForNonExistingField() {
122122
Assert.Null(provHistory.GetFieldValue("title", new Date(1000, 1, 1)));
123123
}
124124

125+
[Fact]
126+
public void NegativeDatesAreSupported() {
127+
var reader = new BufferedReader(
128+
@"= { #Sarkel
129+
culture = mykenian
130+
-750.1.2 = {
131+
culture = macedonian
132+
}
133+
-100.1.2 = {
134+
culture = greek
135+
}
136+
50.3.4 = {
137+
culture = roman
138+
}
139+
}");
140+
141+
var provHistoryFactory = new HistoryFactory.HistoryFactoryBuilder()
142+
.WithSimpleField("culture", "culture", null)
143+
.Build();
144+
145+
var provHistory = provHistoryFactory.GetHistory(reader);
146+
147+
Assert.Equal("mykenian", provHistory.GetFieldValue("culture", new Date(-800, 1, 1))!.ToString());
148+
Assert.Equal("macedonian", provHistory.GetFieldValue("culture", new Date(-750, 1, 2))!.ToString());
149+
Assert.Equal("macedonian", provHistory.GetFieldValue("culture", new Date(-600, 1, 2))!.ToString());
150+
Assert.Equal("greek", provHistory.GetFieldValue("culture", new Date(-100, 1, 2))!.ToString());
151+
Assert.Equal("roman", provHistory.GetFieldValue("culture", new Date(50, 3, 4))!.ToString());
152+
}
153+
125154
[Fact]
126155
public void HistoryCanBeReadFromMultipleItems() {
127156
var reader1 = new BufferedReader(

ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/extra_landed_titles.txt

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ e_mongolia = {
33

44
k_mongolia = {
55
capital = c_karakorum
6+
7+
c_karakorum = {} # Just for the capital entry to be accepted by the converter.
68
}
79

810
k_jubu = {}

ImperatorToCK3/CK3/Characters/CharacterCollection.cs

+23
Original file line numberDiff line numberDiff line change
@@ -846,4 +846,27 @@ public void RemoveUndefinedTraits(TraitMapper traitMapper) {
846846
}
847847
}
848848
}
849+
850+
public void RemoveInvalidDynastiesFromHistory(DynastyCollection dynasties) {
851+
Logger.Info("Removing invalid dynasties from CK3 character history...");
852+
853+
var ck3Characters = this.Where(c => !c.FromImperator).ToArray();
854+
var validDynastyIds = dynasties.Select(d => d.Id).ToHashSet();
855+
856+
foreach (var character in ck3Characters) {
857+
if (!character.History.Fields.TryGetValue("dynasty", out var dynastyField)) {
858+
continue;
859+
}
860+
861+
dynastyField.RemoveAllEntries(value => {
862+
var dynastyId = value.ToString()?.RemQuotes();
863+
864+
if (string.IsNullOrWhiteSpace(dynastyId)) {
865+
return true;
866+
}
867+
868+
return !validDynastyIds.Contains(dynastyId);
869+
});
870+
}
871+
}
849872
}

ImperatorToCK3/CK3/Characters/CharactersLoader.cs

+42-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using commonItems;
22
using commonItems.Mods;
33
using Open.Collections.Synchronized;
4+
using System.Collections.Generic;
45
using System.Linq;
56

67
namespace ImperatorToCK3.CK3.Characters;
@@ -33,9 +34,21 @@ public void LoadCK3Characters(ModFilesystem ck3ModFS, Date bookmarkDate) {
3334
"set_relation_ward", "set_relation_mentor",
3435
"add_opinion", "make_concubine",
3536
];
36-
string[] fieldsToClear = ["friends", "best_friends", "lovers", "rivals", "nemesis", "primary_title", "dna"];
37+
string[] fieldsToClear = [
38+
"friends", "best_friends", "lovers", "rivals", "nemesis",
39+
"primary_title", "dna", "spawn_army", "add_character_modifier", "languages",
40+
"claims",
41+
];
3742

43+
var femaleCharacterIds = loadedCharacters.Where(c => c.Female).Select(c => c.Id).ToHashSet();
44+
var maleCharacterIds = loadedCharacters.Select(c => c.Id).Except(femaleCharacterIds).ToHashSet();
45+
3846
foreach (var character in loadedCharacters) {
47+
// Clear some fields we don't need.
48+
foreach (var fieldName in fieldsToClear) {
49+
character.History.Fields[fieldName].RemoveAllEntries();
50+
}
51+
3952
// Remove post-bookmark history except for births and deaths.
4053
foreach (var field in character.History.Fields) {
4154
if (field.Id == "birth" || field.Id == "death") {
@@ -58,7 +71,9 @@ public void LoadCK3Characters(ModFilesystem ck3ModFS, Date bookmarkDate) {
5871
deathField.RemoveAllEntries();
5972
deathField.AddEntryToHistory(deathDate, "death", value: true);
6073
}
61-
74+
75+
RemoveInvalidMotherAndFatherEntries(character, femaleCharacterIds, maleCharacterIds);
76+
6277
// Remove dated name changes like 64.10.13 = { name = "Linus" }
6378
var nameField = character.History.Fields["name"];
6479
nameField.RemoveHistoryPastDate(birthDate);
@@ -67,14 +82,34 @@ public void LoadCK3Characters(ModFilesystem ck3ModFS, Date bookmarkDate) {
6782
character.History.Fields["effects"].RemoveAllEntries(
6883
entry => irrelevantEffects.Any(effect => entry.ToString()?.Contains(effect) ?? false));
6984

70-
// Clear some fields we don't need.
71-
foreach (var fieldName in fieldsToClear) {
72-
character.History.Fields[fieldName].RemoveAllEntries();
73-
}
74-
7585
character.InitSpousesCache();
7686
character.InitConcubinesCache();
7787
character.UpdateChildrenCacheOfParents();
7888
}
7989
}
90+
91+
private static void RemoveInvalidMotherAndFatherEntries(Character character, HashSet<string> femaleCharacterIds, HashSet<string> maleCharacterIds) {
92+
// Remove wrong sex mother and father references (male mothers, female fathers).
93+
var motherField = character.History.Fields["mother"];
94+
motherField.RemoveAllEntries(value => {
95+
string? motherId = value.ToString()?.RemQuotes();
96+
if (motherId is null || !femaleCharacterIds.Contains(motherId)) {
97+
Logger.Debug($"Removing invalid mother {motherId} from character {character.Id}");
98+
return true;
99+
}
100+
101+
return false;
102+
});
103+
104+
var fatherField = character.History.Fields["father"];
105+
fatherField.RemoveAllEntries(value => {
106+
string? fatherId = value.ToString()?.RemQuotes();
107+
if (fatherId is null || !maleCharacterIds.Contains(fatherId)) {
108+
Logger.Debug($"Removing invalid father {fatherId} from character {character.Id}");
109+
return true;
110+
}
111+
112+
return false;
113+
});
114+
}
80115
}

ImperatorToCK3/CK3/Characters/DNAFactory.cs

+3
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,10 @@ AccessoryGene ck3Gene
395395
Logger.Warn($"No object mappings found for {geneInfo.ObjectName} in gene {irGeneName}!");
396396
return null;
397397
}
398+
399+
// Prefer using the smallest template that contains the object.
398400
var ck3GeneTemplate = ck3Gene.GeneTemplates
401+
.OrderBy(t => t.ObjectCountForAgeSex(irCharacter.AgeSex))
399402
.FirstOrDefault(t => t.ContainsObjectForAgeSex(irCharacter.AgeSex, convertedSetEntry));
400403
if (ck3GeneTemplate is null) {
401404
Logger.Warn($"No template found for {convertedSetEntry} in CK3 gene {ck3Gene.Id}!");

ImperatorToCK3/CK3/Cultures/CultureCollection.cs

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ private void InitCultureDataParser(ColorFactory colorFactory, OrderedDictionary<
3434
});
3535
cultureDataParser.RegisterKeyword("parents", reader => {
3636
cultureData.ParentCultureIds = reader.GetStrings().ToOrderedSet();
37+
38+
if (cultureData.ParentCultureIds.Count > 2) {
39+
Logger.Warn("Found a culture that has more than 2 parents! Only the first 2 will be used.");
40+
cultureData.ParentCultureIds = cultureData.ParentCultureIds.Take(2).ToOrderedSet();
41+
}
3742
});
3843
cultureDataParser.RegisterKeyword("heritage", reader => {
3944
var heritageId = reader.GetString();

ImperatorToCK3/CK3/Dynasties/Dynasty.cs

+20-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using System.Diagnostics.CodeAnalysis;
1212
using System.Linq;
1313

14+
using ImperatorCharacter = ImperatorToCK3.Imperator.Characters.Character;
15+
1416
namespace ImperatorToCK3.CK3.Dynasties;
1517

1618
[SerializationByProperties]
@@ -32,10 +34,10 @@ public Dynasty(Family irFamily, CharacterCollection irCharacters, CulturesDB irC
3234
ck3Member?.SetDynastyId(Id, date: null);
3335
}
3436

35-
SetLocFromImperatorFamilyName(irFamily.GetMaleForm(irCulturesDB), irLocDB, ck3LocDB);
37+
SetLocFromImperatorFamilyName(irFamily.GetMaleForm(irCulturesDB), imperatorMembers, irLocDB, ck3LocDB);
3638
}
3739

38-
public Dynasty(CK3.Characters.Character character, string irFamilyName, CulturesDB irCulturesDB, LocDB irLocDB, CK3LocDB ck3LocDB, Date date) {
40+
public Dynasty(CK3.Characters.Character character, string irFamilyName, ImperatorCharacter[] irMembers, CulturesDB irCulturesDB, LocDB irLocDB, CK3LocDB ck3LocDB, Date date) {
3941
FromImperator = true;
4042
Id = $"dynn_irtock3_from_{character.Id}";
4143
Name = Id;
@@ -47,7 +49,7 @@ public Dynasty(CK3.Characters.Character character, string irFamilyName, Cultures
4749

4850
character.SetDynastyId(Id, null);
4951

50-
SetLocFromImperatorFamilyName(Family.GetMaleForm(irFamilyName, irCulturesDB), irLocDB, ck3LocDB);
52+
SetLocFromImperatorFamilyName(Family.GetMaleForm(irFamilyName, irCulturesDB), irMembers, irLocDB, ck3LocDB);
5153
}
5254

5355
public Dynasty(string dynastyId, BufferedReader dynastyReader) {
@@ -125,8 +127,9 @@ private void SetCultureFromImperator(Family irFamily, IReadOnlyList<Character> i
125127
Logger.Warn($"Couldn't determine culture for dynasty {Id}, needs manual setting!");
126128
}
127129

128-
private void SetLocFromImperatorFamilyName(string irFamilyLocKey, LocDB irLocDB, CK3LocDB ck3LocDB) {
130+
private void SetLocFromImperatorFamilyName(string irFamilyLocKey, ImperatorCharacter[] irMembers, LocDB irLocDB, CK3LocDB ck3LocDB) {
129131
var irFamilyLoc = irLocDB.GetLocBlockForKey(irFamilyLocKey);
132+
130133
var ck3NameLoc = ck3LocDB.GetOrCreateLocBlock(Name);
131134
if (irFamilyLoc is not null) {
132135
ck3NameLoc.CopyFrom(irFamilyLoc);
@@ -137,6 +140,19 @@ private void SetLocFromImperatorFamilyName(string irFamilyLocKey, LocDB irLocDB,
137140
return !string.IsNullOrEmpty(other) ? other : irFamilyLoc.Id;
138141
});
139142
} else { // fallback: use unlocalized Imperator family key
143+
// If the loc key is an empty string, try using a family name from the family's members.
144+
if (string.IsNullOrEmpty(irFamilyLocKey)) {
145+
foreach (var irMember in irMembers) {
146+
if (irMember.FamilyName is null) {
147+
continue;
148+
}
149+
150+
Logger.Debug($"Dynasty {Id} has an empty loc key! Using family name from member \"{irMember.FamilyName}\".");
151+
ck3NameLoc[ConverterGlobals.PrimaryLanguage] = irMember.FamilyName;
152+
return;
153+
}
154+
}
155+
140156
Logger.Debug($"Dynasty {Id} has no localization for name \"{irFamilyLocKey}\"! Using unlocalized name.");
141157
ck3NameLoc[ConverterGlobals.PrimaryLanguage] = irFamilyLocKey;
142158
}

ImperatorToCK3/CK3/Dynasties/DynastyCollection.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ private void CreateDynastiesForCharactersFromMinorFamilies(Imperator.World irWor
7777
}
7878

7979
// Neither character nor their father have a dynasty, so we need to create a new one.
80-
var newDynasty = new Dynasty(ck3Character, irFamilyName, irWorld.CulturesDB, irLocDB, ck3LocDB, date);
80+
Imperator.Characters.Character[] irFamilyMembers = [irCharacter];
81+
var newDynasty = new Dynasty(ck3Character, irFamilyName, irFamilyMembers, irWorld.CulturesDB, irLocDB, ck3LocDB, date);
8182
AddOrReplace(newDynasty);
8283
++createdDynastiesCount;
8384
}

ImperatorToCK3/CK3/Religions/DoctrineCategory.cs

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace ImperatorToCK3.CK3.Religions;
88
public sealed class DoctrineCategory : IIdentifiable<string> {
99
public string Id { get; }
1010
public string? GroupId { get; private set; }
11+
public int NumberOfPicks { get; private set; } = 1;
12+
1113
private readonly OrderedSet<string> doctrineIds = new();
1214
public IReadOnlyCollection<string> DoctrineIds => doctrineIds.ToImmutableArray();
1315

@@ -16,6 +18,7 @@ public DoctrineCategory(string id, BufferedReader categoryReader) {
1618

1719
var parser = new Parser();
1820
parser.RegisterKeyword("group", reader => GroupId = reader.GetString());
21+
parser.RegisterKeyword("number_of_picks", reader => NumberOfPicks = reader.GetInt());
1922
parser.RegisterRegex(CommonRegexes.String, (reader, doctrineId) => {
2023
doctrineIds.Add(doctrineId);
2124
ParserHelpers.IgnoreItem(reader);

ImperatorToCK3/CK3/Religions/Faith.cs

+25-7
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,25 @@ public Faith(string id, FaithData faithData, Religion religion) {
2626
attributes = [.. faithData.Attributes];
2727

2828
// Fixup for issue found in TFE: add reformed_icon if faith has unreformed_faith_doctrine.
29-
if (DoctrineIds.Contains("unreformed_faith_doctrine") && !attributes.Any(pair => pair.Key == "reformed_icon")) {
29+
if (DoctrineIds.Contains("unreformed_faith_doctrine") && attributes.All(pair => pair.Key != "reformed_icon")) {
3030
// Use the icon attribute.
3131
var icon = attributes.FirstOrDefault(pair => pair.Key == "icon");
3232
attributes = [.. attributes, new KeyValuePair<string, StringOfItem>("reformed_icon", icon.Value)];
3333
}
34+
35+
// Fix a faith having more doctrines in the same category than allowed.
36+
foreach (var category in religion.ReligionCollection.DoctrineCategories) {
37+
var doctrinesInCategory = DoctrineIds.Where(d => category.DoctrineIds.Contains(d)).ToArray();
38+
if (doctrinesInCategory.Length > category.NumberOfPicks) {
39+
Logger.Warn($"Faith {Id} has too many doctrines in category {category.Id}: " +
40+
$"{string.Join(", ", doctrinesInCategory)}. Keeping the last {category.NumberOfPicks} of them.");
41+
42+
DoctrineIds.ExceptWith(doctrinesInCategory);
43+
foreach (var doctrine in doctrinesInCategory.Reverse().Take(category.NumberOfPicks)) {
44+
DoctrineIds.Add(doctrine);
45+
}
46+
}
47+
}
3448
}
3549

3650
private readonly OrderedSet<string> holySiteIds;
@@ -78,17 +92,21 @@ public string Serialize(string indent, bool withBraces) {
7892
return sb.ToString();
7993
}
8094

81-
public string? GetDoctrineIdForDoctrineCategoryId(string doctrineCategoryId) {
95+
public OrderedSet<string> GetDoctrineIdsForDoctrineCategoryId(string doctrineCategoryId) {
8296
var category = Religion.ReligionCollection.DoctrineCategories[doctrineCategoryId];
83-
return GetDoctrineIdForDoctrineCategory(category);
97+
return GetDoctrineIdsForDoctrineCategory(category);
8498
}
8599

86-
private string? GetDoctrineIdForDoctrineCategory(DoctrineCategory category) {
100+
private OrderedSet<string> GetDoctrineIdsForDoctrineCategory(DoctrineCategory category) {
87101
var potentialDoctrineIds = category.DoctrineIds;
88102

89103
// Look in faith first. If not found, look in religion.
90-
var matchingInFaith = DoctrineIds.Intersect(potentialDoctrineIds).LastOrDefault();
91-
return matchingInFaith ?? Religion.DoctrineIds.Intersect(potentialDoctrineIds).LastOrDefault();
104+
var matchingInFaith = DoctrineIds.Intersect(potentialDoctrineIds).ToOrderedSet();
105+
if (matchingInFaith.Any()) {
106+
return matchingInFaith;
107+
} else {
108+
return Religion.DoctrineIds.Intersect(potentialDoctrineIds).ToOrderedSet();
109+
}
92110
}
93111

94112
public bool HasDoctrine(string doctrineId) {
@@ -98,6 +116,6 @@ public bool HasDoctrine(string doctrineId) {
98116
return false;
99117
}
100118

101-
return GetDoctrineIdForDoctrineCategory(category) == doctrineId;
119+
return GetDoctrineIdsForDoctrineCategory(category).Contains(doctrineId);
102120
}
103121
}

ImperatorToCK3/CK3/Religions/HolySite.cs

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ public HolySite(string id, BufferedReader holySiteReader, Title.LandedTitles lan
5454
if (parsedBaronyId is not null) {
5555
Barony = landedTitles[parsedBaronyId];
5656
}
57+
58+
// Fix "barony not in specified county" errors reported by ck3-tiger.
59+
if (Barony is not null && County is not null && Barony.DeJureLiege != County) {
60+
string baseMessage = $"Holy site {Id} has barony {Barony.Id} not in specified county {County.Id}.";
61+
var correctCounty = Barony.DeJureLiege;
62+
if (correctCounty is not null) {
63+
Logger.Debug($"{baseMessage} Setting county to {correctCounty.Id}.");
64+
County = correctCounty;
65+
} else {
66+
Logger.Warn($"{baseMessage} Cannot find correct county.");
67+
}
68+
}
5769
}
5870

5971
private static string GenerateHolySiteId(Title barony, Faith faith) {

ImperatorToCK3/CK3/Religions/Religion.cs

+14
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ public Religion(string id, BufferedReader religionReader, ReligionCollection rel
3434
attributes.Add(new KeyValuePair<string, StringOfItem>(keyword, reader.GetStringOfItem()));
3535
});
3636
religionParser.ParseStream(religionReader);
37+
38+
// Fix a religion having more doctrines in the same category than allowed.
39+
foreach (var category in religions.DoctrineCategories) {
40+
var doctrinesInCategory = DoctrineIds.Where(d => category.DoctrineIds.Contains(d)).ToArray();
41+
if (doctrinesInCategory.Length > category.NumberOfPicks) {
42+
Logger.Warn($"Religion {Id} has too many doctrines in category {category.Id}: " +
43+
$"{string.Join(", ", doctrinesInCategory)}. Keeping the last {category.NumberOfPicks} of them.");
44+
45+
DoctrineIds.ExceptWith(doctrinesInCategory);
46+
foreach (var doctrine in doctrinesInCategory.Reverse().Take(category.NumberOfPicks)) {
47+
DoctrineIds.Add(doctrine);
48+
}
49+
}
50+
}
3751
}
3852
private void LoadFaith(string faithId, BufferedReader faithReader) {
3953
faithData = new FaithData();

ImperatorToCK3/CK3/Religions/ReligionCollection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ Date date
300300

301301
var aliveFaithsWithSpiritualHeadDoctrine = Faiths
302302
.Where(f => aliveCharacterFaithIds.Contains(f.Id) || provinceFaithIds.Contains(f.Id))
303-
.Where(f => f.GetDoctrineIdForDoctrineCategoryId("doctrine_head_of_faith") == "doctrine_spiritual_head")
303+
.Where(f => f.GetDoctrineIdsForDoctrineCategoryId("doctrine_head_of_faith").Contains("doctrine_spiritual_head"))
304304
.ToImmutableList();
305305

306306
foreach (var faith in aliveFaithsWithSpiritualHeadDoctrine) {

0 commit comments

Comments
 (0)