diff --git a/ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs b/ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs index bd12ba1a8..45b9d6ec2 100644 --- a/ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs +++ b/ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs @@ -582,4 +582,29 @@ public void GetBaronyForProvinceReturnsCorrectBaronyOrNullWhenNotFound() { Assert.Equal("b_barony3", titles.GetBaronyForProvince(3)?.Id); Assert.Null(titles.GetBaronyForProvince(4)); } -} \ No newline at end of file + + [Fact] + public void TitlesCanBeExpandedInOtherFiles() { + var titles = new Title.LandedTitles(); + titles.LoadTitles(ck3ModFS, new TestCK3LocDB()); + + // e_mongolia's color is defined in base_landed_titles.txt. + // But its capital is defined in extra_landed_titles.txt. + // If both are properly set, it means that we're correctly loading a title from multiple files. + var mongoliaEmpire = titles["e_mongolia"]; + Assert.Equal(new Color(90, 90, 240), mongoliaEmpire.Color1); + Assert.Equal("c_karakorum", mongoliaEmpire.CapitalCountyId); + + // It has k_mongolia and k_angara defined as de jure vassals in base_landed_titles.txt. + // It also has k_jubu defined as a de jure vassal in extra_landed_titles.txt. + Assert.Equal(3, mongoliaEmpire.DeJureVassals.Count); + + // k_mongolia's color is defined in base_landed_titles.txt. + // But its capital is defined in extra_landed_titles.txt. + // This checks if we can correctly load lower rank titles (nested in the structure) from multiple files. + var mongoliaKingdom = titles["k_mongolia"]; + Assert.Equal(new Color(20, 65, 25), mongoliaKingdom.Color1); + Assert.Equal("c_karakorum", mongoliaKingdom.CapitalCountyId); + } +} + diff --git a/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/base_landed_titles.txt b/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/base_landed_titles.txt new file mode 100644 index 000000000..cf4d1592e --- /dev/null +++ b/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/base_landed_titles.txt @@ -0,0 +1,9 @@ +e_mongolia = { + color = { 90 90 240 } + + k_mongolia = { + color = { 20 65 25 } + } + + k_angara = {} +} \ No newline at end of file diff --git a/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/extra_landed_titles.txt b/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/extra_landed_titles.txt new file mode 100644 index 000000000..96a5fdfa9 --- /dev/null +++ b/ImperatorToCK3.UnitTests/TestFiles/LandedTitlesTests/CK3/game/common/landed_titles/extra_landed_titles.txt @@ -0,0 +1,9 @@ +e_mongolia = { + capital = c_karakorum + + k_mongolia = { + capital = c_karakorum + } + + k_jubu = {} +} \ No newline at end of file diff --git a/ImperatorToCK3/CK3/Titles/LandedTitles.cs b/ImperatorToCK3/CK3/Titles/LandedTitles.cs index 7134e9827..3ee5cfe23 100644 --- a/ImperatorToCK3/CK3/Titles/LandedTitles.cs +++ b/ImperatorToCK3/CK3/Titles/LandedTitles.cs @@ -119,9 +119,14 @@ private void RegisterKeys(Parser parser) { Variables[variableName[1..]] = variableValue; }); parser.RegisterRegex(Regexes.TitleId, (reader, titleNameStr) => { - // Pull the titles beneath this one and add them to the lot, overwriting existing ones. - var newTitle = Add(titleNameStr); - newTitle.LoadTitles(reader); + // Pull the titles beneath this one and add them to the lot. + // A title can be defined in multiple files, in that case merge the definitions. + if (TryGetValue(titleNameStr, out var titleToUpdate)) { + titleToUpdate.LoadTitles(reader); + } else { + var newTitle = Add(titleNameStr); + newTitle.LoadTitles(reader); + } }); parser.IgnoreAndLogUnregisteredItems(); } @@ -221,8 +226,7 @@ ImperatorRegionMapper imperatorRegionMapper } public override void Remove(string name) { if (dict.TryGetValue(name, out var titleToErase)) { - var deJureLiege = titleToErase.DeJureLiege; - deJureLiege?.DeJureVassals.Remove(name); + titleToErase.DeJureLiege = null; // Remove two-way liege-vassal link. foreach (var vassal in titleToErase.DeJureVassals) { vassal.DeJureLiege = null; diff --git a/ImperatorToCK3/CK3/Titles/Title.cs b/ImperatorToCK3/CK3/Titles/Title.cs index 2786cf952..c53a66fa8 100644 --- a/ImperatorToCK3/CK3/Titles/Title.cs +++ b/ImperatorToCK3/CK3/Titles/Title.cs @@ -940,9 +940,9 @@ public Title? DeJureLiege { // direct de jure liege title Logger.Warn($"Cannot set de jure liege {value} to {Id}: rank is not higher!"); return; } - deJureLiege?.DeJureVassals.Remove(Id); + deJureLiege?.deJureVassals.Remove(Id); deJureLiege = value; - value?.DeJureVassals.AddOrReplace(this); + value?.deJureVassals.AddOrReplace(this); } } public Title? GetDeFactoLiege(Date date) { // direct de facto liege title @@ -978,7 +978,8 @@ public void SetDeFactoLiege(Title? newLiege, Date date) { } } - [SerializeOnlyValue] public TitleCollection DeJureVassals { get; } = new(); // DIRECT de jure vassals + private readonly TitleCollection deJureVassals = []; + [SerializeOnlyValue] public IReadOnlyTitleCollection DeJureVassals => deJureVassals; // DIRECT de jure vassals public IDictionary GetDeJureVassalsAndBelow() { return GetDeJureVassalsAndBelow("bcdke"); } @@ -1090,16 +1091,21 @@ public ICollection GetSuccessionLaws(Date date) { private void RegisterKeys(Parser parser) { parser.RegisterRegex(Regexes.TitleId, (reader, titleNameStr) => { - // Pull the titles beneath this one and add them to the lot, overwriting existing ones. - var newTitle = parentCollection.Add(titleNameStr); - newTitle.LoadTitles(reader); + // Pull the titles beneath this one and add them to the lot. + // A title can be defined in multiple files, in that case merge the definitions. + if (parentCollection.TryGetValue(titleNameStr, out var childTitle)) { + childTitle.LoadTitles(reader); + } else { + childTitle = parentCollection.Add(titleNameStr); + childTitle.LoadTitles(reader); + } - if (newTitle.Rank == TitleRank.barony && string.IsNullOrEmpty(CapitalBaronyId)) { + if (childTitle.Rank == TitleRank.barony && string.IsNullOrEmpty(CapitalBaronyId)) { // title is a barony, and no other barony has been found in this scope yet - CapitalBaronyId = newTitle.Id; + CapitalBaronyId = childTitle.Id; } - - newTitle.DeJureLiege = this; + + childTitle.DeJureLiege = this; }); parser.RegisterKeyword("definite_form", reader => HasDefiniteForm = reader.GetBool()); parser.RegisterKeyword("ruler_uses_title_name", reader => RulerUsesTitleName = reader.GetBool()); diff --git a/ImperatorToCK3/CK3/Titles/TitleCollection.cs b/ImperatorToCK3/CK3/Titles/TitleCollection.cs index 8ec312bf3..f2897bb6a 100644 --- a/ImperatorToCK3/CK3/Titles/TitleCollection.cs +++ b/ImperatorToCK3/CK3/Titles/TitleCollection.cs @@ -1,5 +1,10 @@ using commonItems.Collections; +using System.Collections.Generic; namespace ImperatorToCK3.CK3.Titles; -public class TitleCollection : IdObjectCollection; \ No newline at end of file +public interface IReadOnlyTitleCollection : IReadOnlyCollection { + public bool ContainsKey(string key); +} + +public class TitleCollection : IdObjectCollection<string, Title>, IReadOnlyTitleCollection; \ No newline at end of file