From 881f9cc2fda0fb8e5f8c89e5912a0d2fa9ab0a66 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 11:41:28 +0800 Subject: [PATCH 01/11] feat(tests): add 33 V1_3_15 XML roundtrip tests Add comprehensive roundtrip tests for V1_3_15 XML parsing covering: - String types: ActionStrings, CompanionStrings, VoiceStrings, WorldLoreStrings, CommentOnActionStrings, ModuleStrings - Simple entities: Bandits, Caravans, ConversationAnimations, MeetingScenes, SkeletonScales, SPBattleScenes - Medium complexity: CommentStrings, ConceptStrings, ConversationScenes, PartyTemplates, TraitStrings, ObsoleteCharacters - Medium lists: SandboxBodyProperties, SandboxCheats, SandboxSkillSets, SettlementTrackInstruments, SettlementTracks, SPProjects - Complex nested: EducationEquipmentTemplates, SandboxEquipmentSets, EducationCharacterTemplates, Heroes, LocationComplexTemplates - Complex with many attributes: SPGenericCharacters, SpKingdoms, SPSpecialCharacters, SPWorkshops Each test verifies XML deserialize/serialize roundtrip preserves data exactly. --- .../V1_3_15/ActionStringsRoundtripTests.cs | 22 +++++++++++++++++++ .../Models/V1_3_15/BanditsRoundtripTests.cs | 20 +++++++++++++++++ .../Models/V1_3_15/CaravansRoundtripTests.cs | 20 +++++++++++++++++ .../CommentOnActionStringsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/CommentStringsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/CompanionStringsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/ConceptStringsRoundtripTests.cs | 20 +++++++++++++++++ .../ConversationAnimationsRoundtripTests.cs | 20 +++++++++++++++++ .../ConversationScenesRoundtripTests.cs | 20 +++++++++++++++++ ...ucationCharacterTemplatesRoundtripTests.cs | 20 +++++++++++++++++ ...ucationEquipmentTemplatesRoundtripTests.cs | 22 +++++++++++++++++++ .../Models/V1_3_15/HeroesRoundtripTests.cs | 20 +++++++++++++++++ .../LocationComplexTemplatesRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/MeetingScenesRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/ModuleStringsRoundtripTests.cs | 20 +++++++++++++++++ .../ObsoleteCharactersRoundtripTests.cs | 22 +++++++++++++++++++ .../V1_3_15/PartyTemplatesRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SPBattleScenesRoundtripTests.cs | 20 +++++++++++++++++ .../SPGenericCharactersRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SPProjectsRoundtripTests.cs | 20 +++++++++++++++++ .../SPSpecialCharactersRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SPWorkshopsRoundtripTests.cs | 20 +++++++++++++++++ .../SandboxBodyPropertiesRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SandboxCheatsRoundtripTests.cs | 20 +++++++++++++++++ .../SandboxEquipmentSetsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SandboxSkillSetsRoundtripTests.cs | 20 +++++++++++++++++ ...ettlementTrackInstrumentsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SettlementTracksRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SkeletonScalesRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/SpKingdomsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/TraitStringsRoundtripTests.cs | 22 +++++++++++++++++++ .../V1_3_15/VoiceStringsRoundtripTests.cs | 20 +++++++++++++++++ .../V1_3_15/WorldLoreStringsRoundtripTests.cs | 20 +++++++++++++++++ 33 files changed, 668 insertions(+) create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ActionStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/CaravansRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentOnActionStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/CompanionStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ConceptStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationAnimationsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationScenesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationCharacterTemplatesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationEquipmentTemplatesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/LocationComplexTemplatesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/MeetingScenesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ModuleStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/ObsoleteCharactersRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/PartyTemplatesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SPBattleScenesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SPGenericCharactersRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SPProjectsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SPSpecialCharactersRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SPWorkshopsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxBodyPropertiesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxCheatsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxEquipmentSetsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxSkillSetsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTrackInstrumentsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTracksRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SkeletonScalesRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/SpKingdomsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/TraitStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/VoiceStringsRoundtripTests.cs create mode 100644 BannerlordModEditor.Common.Tests/Models/V1_3_15/WorldLoreStringsRoundtripTests.cs diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ActionStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ActionStringsRoundtripTests.cs new file mode 100644 index 0000000..a5eea5c --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ActionStringsRoundtripTests.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ActionStringsRoundtripTests + { + [Fact] + public async Task ActionStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "action_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs new file mode 100644 index 0000000..c17bc89 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class BanditsRoundtripTests + { + [Fact] + public async Task Bandits_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "bandits.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/CaravansRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CaravansRoundtripTests.cs new file mode 100644 index 0000000..3da09c5 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CaravansRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class CaravansRoundtripTests + { + [Fact] + public async Task Caravans_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "caravans.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentOnActionStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentOnActionStringsRoundtripTests.cs new file mode 100644 index 0000000..7763989 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentOnActionStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class CommentOnActionStringsRoundtripTests + { + [Fact] + public async Task CommentOnActionStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "comment_on_action_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentStringsRoundtripTests.cs new file mode 100644 index 0000000..d025e5d --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CommentStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class CommentStringsRoundtripTests + { + [Fact] + public async Task CommentStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "comment_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/CompanionStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CompanionStringsRoundtripTests.cs new file mode 100644 index 0000000..0401fa7 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/CompanionStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class CompanionStringsRoundtripTests + { + [Fact] + public async Task CompanionStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "companion_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConceptStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConceptStringsRoundtripTests.cs new file mode 100644 index 0000000..a762c01 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConceptStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ConceptStringsRoundtripTests + { + [Fact] + public async Task ConceptStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "concept_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} \ No newline at end of file diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationAnimationsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationAnimationsRoundtripTests.cs new file mode 100644 index 0000000..1b46726 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationAnimationsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ConversationAnimationsRoundtripTests + { + [Fact] + public async Task ConversationAnimations_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "conversation_animations.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationScenesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationScenesRoundtripTests.cs new file mode 100644 index 0000000..84de2df --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ConversationScenesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ConversationScenesRoundtripTests + { + [Fact] + public async Task ConversationScenes_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "conversation_scenes.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationCharacterTemplatesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationCharacterTemplatesRoundtripTests.cs new file mode 100644 index 0000000..1c0e0c0 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationCharacterTemplatesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class EducationCharacterTemplatesRoundtripTests + { + [Fact] + public async Task EducationCharacterTemplates_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "education_character_templates.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationEquipmentTemplatesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationEquipmentTemplatesRoundtripTests.cs new file mode 100644 index 0000000..f2a37df --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/EducationEquipmentTemplatesRoundtripTests.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class EducationEquipmentTemplatesRoundtripTests + { + [Fact] + public async Task EducationEquipmentTemplates_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "education_equipment_templates.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs new file mode 100644 index 0000000..7952d09 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class HeroesRoundtripTests + { + [Fact] + public async Task Heroes_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "heroes.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} \ No newline at end of file diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/LocationComplexTemplatesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/LocationComplexTemplatesRoundtripTests.cs new file mode 100644 index 0000000..f819029 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/LocationComplexTemplatesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class LocationComplexTemplatesRoundtripTests + { + [Fact] + public async Task LocationComplexTemplates_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "location_complex_templates.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/MeetingScenesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/MeetingScenesRoundtripTests.cs new file mode 100644 index 0000000..9ac1d92 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/MeetingScenesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class MeetingScenesRoundtripTests + { + [Fact] + public async Task MeetingScenes_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "meeting_scenes.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ModuleStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ModuleStringsRoundtripTests.cs new file mode 100644 index 0000000..af8c9f1 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ModuleStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ModuleStringsRoundtripTests + { + [Fact] + public async Task ModuleStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "module_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/ObsoleteCharactersRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ObsoleteCharactersRoundtripTests.cs new file mode 100644 index 0000000..005ae39 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/ObsoleteCharactersRoundtripTests.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class ObsoleteCharactersRoundtripTests + { + [Fact] + public async Task ObsoleteCharacters_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "obsolete_characters.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/PartyTemplatesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/PartyTemplatesRoundtripTests.cs new file mode 100644 index 0000000..a706c61 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/PartyTemplatesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class PartyTemplatesRoundtripTests + { + [Fact] + public async Task PartyTemplates_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "partyTemplates.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPBattleScenesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPBattleScenesRoundtripTests.cs new file mode 100644 index 0000000..39cec6f --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPBattleScenesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SPBattleScenesRoundtripTests + { + [Fact] + public async Task SPBattleScenes_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "sp_battle_scenes.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPGenericCharactersRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPGenericCharactersRoundtripTests.cs new file mode 100644 index 0000000..4888cce --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPGenericCharactersRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SPGenericCharactersRoundtripTests + { + [Fact] + public async Task SPGenericCharacters_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "spgenericcharacters.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPProjectsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPProjectsRoundtripTests.cs new file mode 100644 index 0000000..d7b43e3 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPProjectsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SPProjectsRoundtripTests + { + [Fact] + public async Task SPProjects_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "spprojects.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPSpecialCharactersRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPSpecialCharactersRoundtripTests.cs new file mode 100644 index 0000000..0549690 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPSpecialCharactersRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SPSpecialCharactersRoundtripTests + { + [Fact] + public async Task SPSpecialCharacters_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "spspecialcharacters.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPWorkshopsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPWorkshopsRoundtripTests.cs new file mode 100644 index 0000000..3ee9c53 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SPWorkshopsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SPWorkshopsRoundtripTests + { + [Fact] + public async Task SPWorkshops_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "spworkshops.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxBodyPropertiesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxBodyPropertiesRoundtripTests.cs new file mode 100644 index 0000000..8c647a6 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxBodyPropertiesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SandboxBodyPropertiesRoundtripTests + { + [Fact] + public async Task SandboxBodyProperties_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "sandbox_bodyproperties.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxCheatsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxCheatsRoundtripTests.cs new file mode 100644 index 0000000..67ba374 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxCheatsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SandboxCheatsRoundtripTests + { + [Fact] + public async Task SandboxCheats_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "sandbox_cheats.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxEquipmentSetsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxEquipmentSetsRoundtripTests.cs new file mode 100644 index 0000000..22b2ece --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxEquipmentSetsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SandboxEquipmentSetsRoundtripTests + { + [Fact] + public async Task SandboxEquipmentSets_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "sandbox_equipment_sets.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxSkillSetsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxSkillSetsRoundtripTests.cs new file mode 100644 index 0000000..845d458 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SandboxSkillSetsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SandboxSkillSetsRoundtripTests + { + [Fact] + public async Task SandboxSkillSets_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "sandbox_skill_sets.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTrackInstrumentsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTrackInstrumentsRoundtripTests.cs new file mode 100644 index 0000000..eb13074 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTrackInstrumentsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SettlementTrackInstrumentsRoundtripTests + { + [Fact] + public async Task SettlementTrackInstruments_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "settlement_track_instruments.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTracksRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTracksRoundtripTests.cs new file mode 100644 index 0000000..e2563e2 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SettlementTracksRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SettlementTracksRoundtripTests + { + [Fact] + public async Task SettlementTracks_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "settlement_tracks.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SkeletonScalesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SkeletonScalesRoundtripTests.cs new file mode 100644 index 0000000..492a320 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SkeletonScalesRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SkeletonScalesRoundtripTests + { + [Fact] + public async Task SkeletonScales_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "skeleton_scales.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/SpKingdomsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SpKingdomsRoundtripTests.cs new file mode 100644 index 0000000..d78d8f7 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/SpKingdomsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class SpKingdomsRoundtripTests + { + [Fact] + public async Task SpKingdoms_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "spkingdoms.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/TraitStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/TraitStringsRoundtripTests.cs new file mode 100644 index 0000000..6c803bf --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/TraitStringsRoundtripTests.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class TraitStringsRoundtripTests + { + [Fact] + public async Task TraitStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "trait_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} \ No newline at end of file diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/VoiceStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/VoiceStringsRoundtripTests.cs new file mode 100644 index 0000000..b40d399 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/VoiceStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class VoiceStringsRoundtripTests + { + [Fact] + public async Task VoiceStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "voice_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/WorldLoreStringsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/WorldLoreStringsRoundtripTests.cs new file mode 100644 index 0000000..56c3a7a --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/WorldLoreStringsRoundtripTests.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading.Tasks; +using BannerlordModEditor.Common.Models.V1_3_15; +using Xunit; + +namespace BannerlordModEditor.Common.Tests.Models.V1_3_15 +{ + public class WorldLoreStringsRoundtripTests + { + [Fact] + public async Task WorldLoreStrings_Roundtrip_ShouldPreserveData() + { + var xmlPath = Path.Combine("TestData", "V1_3_15", "world_lore_strings.xml"); + var originalXml = await File.ReadAllTextAsync(xmlPath); + var obj = XmlTestUtils.Deserialize(originalXml); + var serialized = XmlTestUtils.Serialize(obj, originalXml); + Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); + } + } +} From e694dfd29756d4ee6f8a64001dedc16826878f17 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 14:35:51 +0800 Subject: [PATCH 02/11] fix: strip UTF-8 BOM before XML deserialization to fix 16 failing tests --- BannerlordModEditor.Common.Tests/XmlTestUtils.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/BannerlordModEditor.Common.Tests/XmlTestUtils.cs b/BannerlordModEditor.Common.Tests/XmlTestUtils.cs index 8527a1d..0b5c262 100644 --- a/BannerlordModEditor.Common.Tests/XmlTestUtils.cs +++ b/BannerlordModEditor.Common.Tests/XmlTestUtils.cs @@ -47,11 +47,20 @@ public class XmlComparisonOptions return File.ReadAllText(filePath); } + private static readonly byte[] Utf8Bom = new byte[] { 0xEF, 0xBB, 0xBF }; + public static T Deserialize(string xml) { if (string.IsNullOrEmpty(xml)) throw new ArgumentException("XML cannot be null or empty", nameof(xml)); - + + // Strip UTF-8 BOM if present (causes XmlSerializer to fail with "error in XML document (2,2)") + var bytes = Encoding.UTF8.GetBytes(xml); + if (bytes.Length >= 3 && bytes[0] == Utf8Bom[0] && bytes[1] == Utf8Bom[1] && bytes[2] == Utf8Bom[2]) + { + xml = Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3); + } + var serializer = new XmlSerializer(typeof(T)); using var reader = new StringReader(xml); var obj = (T)serializer.Deserialize(reader)!; From 3a6fc9b9a4ab568b38c97c0ab260afe233f95d38 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 14:44:17 +0800 Subject: [PATCH 03/11] fix: correct XmlRoot attributes to match actual XML root elements Fixed XmlRoot mismatches causing 16 test failures: - SkeletonScales: SkeletonScales -> Scales - Heroes: Characters -> Heroes - Bandits: BanditFactions -> NPCCharacters - Caravans: Caravans -> NPCCharacters - CommentOnActionStrings: base -> strings - CommentStrings: base -> strings - CompanionStrings: base -> strings - ConceptStrings: base -> Concepts - ModuleStrings: base -> strings - TraitStrings: base -> strings - VoiceStrings: base -> strings - WorldLoreStrings: base -> strings - SPBattleScenes: Scenes -> SPBattleScenes --- BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs | 2 +- .../Models/V1_3_15/CommentOnActionStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/CommentStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/CompanionStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/ModuleStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/SPBattleScenes.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/SkeletonScales.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/TraitStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/VoiceStrings.cs | 2 +- BannerlordModEditor.Common/Models/V1_3_15/WorldLoreStrings.cs | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs b/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs index aa49710..be63f9d 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("BanditFactions")] +[XmlRoot("NPCCharacters")] public class BanditFactions { [XmlElement("BanditFaction")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs b/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs index fa99faf..cceb4e8 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("Caravans")] +[XmlRoot("NPCCharacters")] public class Caravans { [XmlElement("Caravan")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/CommentOnActionStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/CommentOnActionStrings.cs index 9af9b8b..9cc7148 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/CommentOnActionStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/CommentOnActionStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class CommentOnActionStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/CommentStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/CommentStrings.cs index 27f45b5..5995a5e 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/CommentStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/CommentStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class CommentStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/CompanionStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/CompanionStrings.cs index aa43822..7d595f9 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/CompanionStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/CompanionStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class CompanionStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs index 9bcb0f0..a2b7970 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("Concepts")] public class ConceptStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs b/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs index cbaa486..20c88b4 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("Characters")] +[XmlRoot("Heroes")] public class Characters { [XmlElement("Character")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/ModuleStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/ModuleStrings.cs index da38c27..c93ef88 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/ModuleStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/ModuleStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class ModuleStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SPBattleScenes.cs b/BannerlordModEditor.Common/Models/V1_3_15/SPBattleScenes.cs index 78c41b5..90d0a85 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SPBattleScenes.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SPBattleScenes.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("Scenes")] +[XmlRoot("SPBattleScenes")] public class SPBattleScenes { [XmlElement("Scene")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SkeletonScales.cs b/BannerlordModEditor.Common/Models/V1_3_15/SkeletonScales.cs index d8a4021..1b287bd 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SkeletonScales.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SkeletonScales.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("SkeletonScales")] +[XmlRoot("Scales")] public class SkeletonScales { [XmlElement("SkeletonScale")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/TraitStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/TraitStrings.cs index eaa614e..f22754b 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/TraitStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/TraitStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class TraitStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/VoiceStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/VoiceStrings.cs index 7302f73..6f77494 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/VoiceStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/VoiceStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class VoiceStrings { [XmlElement("string")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/WorldLoreStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/WorldLoreStrings.cs index 85c1da3..3d2d11e 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/WorldLoreStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/WorldLoreStrings.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("base")] +[XmlRoot("strings")] public class WorldLoreStrings { [XmlElement("string")] From f33aab895ea684d0a2258c26614afa1ac6638fd6 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 14:48:59 +0800 Subject: [PATCH 04/11] fix: correct remaining XmlRoot attributes - SandboxBodyProperties: BodyPropertiesMin -> BodyProperties - PartyTemplates: PartyTemplates -> partyTemplates --- BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs | 2 +- .../Models/V1_3_15/SandboxBodyProperties.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs b/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs index 6933533..d911d03 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs @@ -5,7 +5,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; // partyTemplates.xml - Party Templates for Bannerlord 1.2.9 // Root element: -[XmlRoot("PartyTemplates")] +[XmlRoot("partyTemplates")] public class PartyTemplates { [XmlElement("PartyTemplate")] diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SandboxBodyProperties.cs b/BannerlordModEditor.Common/Models/V1_3_15/SandboxBodyProperties.cs index edb318a..f88dcc1 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SandboxBodyProperties.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SandboxBodyProperties.cs @@ -3,7 +3,7 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -[XmlRoot("BodyPropertiesMin")] +[XmlRoot("BodyProperties")] public class SandboxBodyProperties { [XmlElement("BodyProperty")] From 67f699b4238ca08ffb515c7563dadb4210a579b2 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:02:03 +0800 Subject: [PATCH 05/11] ci: trigger rebuild From 53599e4316eac5eb5138c8b5050c730c3fd3b765 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:21:11 +0800 Subject: [PATCH 06/11] fix: correct XML element/attribute names for 7 failing roundtrip tests - PartyTemplates: XML uses MBPartyTemplate/stacks/PartyTemplateStack (not PartyTemplate/Roles) - Heroes: XML uses with alive/faction/text/father attributes (not ) - Bandits: Use shared NPCCharacter model with full nested content support - Caravans: Use shared NPCCharacter model with is_hero/is_mercenary attributes - ConceptStrings: XML uses not - ConversationAnimations: XML uses IdleAnim/Reaction not Animation - SPGenericCharacters: Refactored to use shared NPCCharacter from SharedNPCCharacters.cs - SharedNPCCharacters: New shared model for NPCCharacter with BodyProperties, hair_tags etc. --- .../Models/V1_3_15/Bandits.cs | 30 +- .../Models/V1_3_15/Caravans.cs | 22 +- .../Models/V1_3_15/ConceptStrings.cs | 14 +- .../Models/V1_3_15/ConversationAnimations.cs | 40 ++- .../Models/V1_3_15/Heroes.cs | 83 ++--- .../Models/V1_3_15/PartyTemplates.cs | 68 ++-- .../Models/V1_3_15/SPGenericCharacters.cs | 152 +-------- .../Models/V1_3_15/SharedNPCCharacters.cs | 315 ++++++++++++++++++ 8 files changed, 412 insertions(+), 312 deletions(-) create mode 100644 BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs b/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs index be63f9d..0f8d9a0 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Bandits.cs @@ -3,32 +3,12 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; +// bandits.xml [XmlRoot("NPCCharacters")] -public class BanditFactions +public class Bandits { - [XmlElement("BanditFaction")] - public List BanditFactionList { get; set; } = new List(); -} - -public class BanditFaction -{ - [XmlAttribute("id")] - public string Id { get; set; } = string.Empty; - - [XmlElement("members")] - public Members? Members { get; set; } - - public bool ShouldSerializeMembers() => Members != null && Members.MemberList.Count > 0; -} + [XmlElement("NPCCharacter")] + public List NPCCharacterList { get; set; } = new List(); -public class Members -{ - [XmlElement("member")] - public List MemberList { get; set; } = new List(); -} - -public class Member -{ - [XmlAttribute("id")] - public string Id { get; set; } = string.Empty; + public bool ShouldSerializeNPCCharacterList() => NPCCharacterList.Count > 0; } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs b/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs index cceb4e8..b636838 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Caravans.cs @@ -3,26 +3,12 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; +// caravans.xml [XmlRoot("NPCCharacters")] public class Caravans { - [XmlElement("Caravan")] - public List CaravanList { get; set; } = new List(); -} - -public class Caravan -{ - [XmlAttribute("id")] - public string Id { get; set; } = string.Empty; - - [XmlAttribute("culture")] - public string Culture { get; set; } = string.Empty; - - [XmlAttribute("member_size")] - public string MemberSize { get; set; } = string.Empty; - - [XmlElement("mesh")] - public string Mesh { get; set; } = string.Empty; + [XmlElement("NPCCharacter")] + public List NPCCharacterList { get; set; } = new List(); - public bool ShouldSerializeMesh() => !string.IsNullOrEmpty(Mesh); + public bool ShouldSerializeNPCCharacterList() => NPCCharacterList.Count > 0; } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs b/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs index a2b7970..b128a17 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/ConceptStrings.cs @@ -6,8 +6,10 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; [XmlRoot("Concepts")] public class ConceptStrings { - [XmlElement("string")] - public List Strings { get; set; } = new List(); + [XmlElement("Concept")] + public List ConceptList { get; set; } = new List(); + + public bool ShouldSerializeConceptList() => ConceptList.Count > 0; } public class ConceptStringItem @@ -15,9 +17,6 @@ public class ConceptStringItem [XmlAttribute("id")] public string Id { get; set; } = string.Empty; - [XmlAttribute("text")] - public string Text { get; set; } = string.Empty; - [XmlAttribute("title")] public string? Title { get; set; } @@ -27,9 +26,12 @@ public class ConceptStringItem [XmlAttribute("link_id")] public string? LinkId { get; set; } + [XmlAttribute("text")] + public string? Text { get; set; } + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); - public bool ShouldSerializeText() => !string.IsNullOrEmpty(Text); public bool ShouldSerializeTitle() => !string.IsNullOrEmpty(Title); public bool ShouldSerializeGroup() => !string.IsNullOrEmpty(Group); public bool ShouldSerializeLinkId() => !string.IsNullOrEmpty(LinkId); + public bool ShouldSerializeText() => !string.IsNullOrEmpty(Text); } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/ConversationAnimations.cs b/BannerlordModEditor.Common/Models/V1_3_15/ConversationAnimations.cs index abbe715..597fd61 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/ConversationAnimations.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/ConversationAnimations.cs @@ -6,16 +6,48 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; [XmlRoot("ConversationAnimations")] public class ConversationAnimations { - [XmlElement("Animation")] - public List AnimationList { get; set; } = new List(); + [XmlElement("IdleAnim")] + public List IdleAnimList { get; set; } = new List(); - public bool ShouldSerializeAnimationList() => AnimationList.Count > 0; + public bool ShouldSerializeIdleAnimList() => IdleAnimList.Count > 0; } -public class ConversationAnimation +public class IdleAnim { [XmlAttribute("id")] public string Id { get; set; } = string.Empty; + [XmlAttribute("action_id_1")] + public string? ActionId1 { get; set; } + + [XmlAttribute("action_id_2")] + public string? ActionId2 { get; set; } + + [XmlElement("Reactions")] + public Reactions? Reactions { get; set; } + + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeActionId1() => !string.IsNullOrEmpty(ActionId1); + public bool ShouldSerializeActionId2() => !string.IsNullOrEmpty(ActionId2); + public bool ShouldSerializeReactions() => Reactions != null && Reactions.ReactionList.Count > 0; +} + +public class Reactions +{ + [XmlElement("Reaction")] + public List ReactionList { get; set; } = new List(); + + public bool ShouldSerializeReactionList() => ReactionList.Count > 0; +} + +public class Reaction +{ + [XmlAttribute("id")] + public string Id { get; set; } = string.Empty; + + [XmlAttribute("action_id")] + public string? ActionId { get; set; } + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeActionId() => !string.IsNullOrEmpty(ActionId); } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs b/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs index 20c88b4..c8c036a 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Heroes.cs @@ -4,79 +4,34 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; [XmlRoot("Heroes")] -public class Characters +public class Heroes { - [XmlElement("Character")] - public List CharacterList { get; set; } = new List(); + [XmlElement("Hero")] + public List HeroList { get; set; } = new List(); + + public bool ShouldSerializeHeroList() => HeroList.Count > 0; } -public class Character +public class Hero { [XmlAttribute("id")] public string Id { get; set; } = string.Empty; - [XmlAttribute("level")] - public string Level { get; set; } = string.Empty; - - [XmlAttribute("name")] - public string Name { get; set; } = string.Empty; - - [XmlAttribute("occupation")] - public string? Occupation { get; set; } - - [XmlElement("face_mesh")] - public FaceMesh? FaceMesh { get; set; } - - [XmlElement("skills")] - public Skills? Skills { get; set; } - - [XmlElement("equipmentSet")] - public EquipmentSet? EquipmentSet { get; set; } - - [XmlElement("formation_class")] - public FormationClass? FormationClass { get; set; } - - public bool ShouldSerializeOccupation() => !string.IsNullOrEmpty(Occupation); - public bool ShouldSerializeFaceMesh() => FaceMesh != null && FaceMesh.HasContent; - public bool ShouldSerializeSkills() => Skills != null && Skills.HasContent; - public bool ShouldSerializeEquipmentSet() => EquipmentSet != null && EquipmentSet.HasContent; - public bool ShouldSerializeFormationClass() => FormationClass != null && FormationClass.HasContent; -} - -public class FaceMesh -{ - [XmlElement("body_parent")] - public BodyParent? BodyParent { get; set; } - - [XmlIgnore] - public bool HasContent => BodyParent != null && BodyParent.HasContent; -} + [XmlAttribute("alive")] + public string? Alive { get; set; } -public class BodyParent -{ - [XmlAttribute("age")] - public string? Age { get; set; } + [XmlAttribute("faction")] + public string? Faction { get; set; } - [XmlIgnore] - public bool HasContent => !string.IsNullOrEmpty(Age); + [XmlAttribute("text")] + public string? Text { get; set; } - public bool ShouldSerializeAge() => !string.IsNullOrEmpty(Age); -} + [XmlAttribute("father")] + public string? Father { get; set; } -public class Skills -{ - [XmlIgnore] - public bool HasContent => false; -} - -public class EquipmentSet -{ - [XmlIgnore] - public bool HasContent => false; -} - -public class FormationClass -{ - [XmlIgnore] - public bool HasContent => false; + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeAlive() => !string.IsNullOrEmpty(Alive); + public bool ShouldSerializeFaction() => !string.IsNullOrEmpty(Faction); + public bool ShouldSerializeText() => !string.IsNullOrEmpty(Text); + public bool ShouldSerializeFather() => !string.IsNullOrEmpty(Father); } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs b/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs index d911d03..7693c6b 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/PartyTemplates.cs @@ -3,67 +3,47 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; -// partyTemplates.xml - Party Templates for Bannerlord 1.2.9 -// Root element: [XmlRoot("partyTemplates")] public class PartyTemplates { - [XmlElement("PartyTemplate")] - public List PartyTemplateList { get; set; } = new List(); + [XmlElement("MBPartyTemplate")] + public List MBPartyTemplateList { get; set; } = new List(); + + public bool ShouldSerializeMBPartyTemplateList() => MBPartyTemplateList.Count > 0; } -// PartyTemplate attributes: -// - id, culture, name, template -public class PartyTemplate +public class MBPartyTemplate { - // Required attributes [XmlAttribute("id")] public string Id { get; set; } = string.Empty; - // Optional attributes - [XmlAttribute("culture")] - public string? Culture { get; set; } - - [XmlAttribute("name")] - public string? Name { get; set; } - - [XmlAttribute("template")] - public string? Template { get; set; } - - // Child elements - [XmlElement("Roles")] - public Roles? Roles { get; set; } + [XmlElement("stacks")] + public Stacks? Stacks { get; set; } - // ShouldSerialize methods for optional attributes - public bool ShouldSerializeCulture() => !string.IsNullOrEmpty(Culture); - public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); - public bool ShouldSerializeTemplate() => !string.IsNullOrEmpty(Template); - public bool ShouldSerializeRoles() => Roles != null && Roles.RoleList.Count > 0; + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeStacks() => Stacks != null && Stacks.PartyTemplateStackList.Count > 0; } -// Roles contains Role elements -public class Roles +public class Stacks { - [XmlElement("Role")] - public List RoleList { get; set; } = new List(); + [XmlElement("PartyTemplateStack")] + public List PartyTemplateStackList { get; set; } = new List(); + + public bool ShouldSerializePartyTemplateStackList() => PartyTemplateStackList.Count > 0; } -// Role attributes: -// - id, type, value -public class Role +public class PartyTemplateStack { - // Required attributes - [XmlAttribute("id")] - public string Id { get; set; } = string.Empty; + [XmlAttribute("min_value")] + public string MinValue { get; set; } = string.Empty; - // Optional attributes - [XmlAttribute("type")] - public string? Type { get; set; } + [XmlAttribute("max_value")] + public string MaxValue { get; set; } = string.Empty; - [XmlAttribute("value")] - public string? Value { get; set; } + [XmlAttribute("troop")] + public string Troop { get; set; } = string.Empty; - // ShouldSerialize methods - public bool ShouldSerializeType() => !string.IsNullOrEmpty(Type); - public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); + public bool ShouldSerializeMinValue() => !string.IsNullOrEmpty(MinValue); + public bool ShouldSerializeMaxValue() => !string.IsNullOrEmpty(MaxValue); + public bool ShouldSerializeTroop() => !string.IsNullOrEmpty(Troop); } diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SPGenericCharacters.cs b/BannerlordModEditor.Common/Models/V1_3_15/SPGenericCharacters.cs index 7be768e..f9ade70 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SPGenericCharacters.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SPGenericCharacters.cs @@ -3,160 +3,10 @@ namespace BannerlordModEditor.Common.Models.V1_3_15; + [XmlRoot("NPCCharacters")] public class SPGenericCharacters { [XmlElement("NPCCharacter")] public List NPCCharacterList { get; set; } = new List(); } - -public class NPCCharacter -{ - [XmlAttribute("id")] - public string Id { get; set; } = string.Empty; - - [XmlAttribute("default_group")] - public string? DefaultGroup { get; set; } - - [XmlAttribute("level")] - public string? Level { get; set; } - - [XmlAttribute("occupation")] - public string? Occupation { get; set; } - - [XmlAttribute("culture")] - public string? Culture { get; set; } - - [XmlAttribute("name")] - public string? Name { get; set; } - - [XmlAttribute("skill_template")] - public string? SkillTemplate { get; set; } - - [XmlAttribute("is_hidden_encyclopedia")] - public string? IsHiddenEncyclopedia { get; set; } - - [XmlElement("face")] - public NPCFace? Face { get; set; } - - [XmlElement("skills")] - public NpcCharacterSkills? Skills { get; set; } - - [XmlElement("Traits")] - public NpcTraits? Traits { get; set; } - - [XmlElement("upgrade_targets")] - public NpcUpgradeTargets? UpgradeTargets { get; set; } - - [XmlElement("Equipments")] - public NpcEquipments? Equipments { get; set; } - - public bool ShouldSerializeDefaultGroup() => !string.IsNullOrEmpty(DefaultGroup); - public bool ShouldSerializeLevel() => !string.IsNullOrEmpty(Level); - public bool ShouldSerializeOccupation() => !string.IsNullOrEmpty(Occupation); - public bool ShouldSerializeCulture() => !string.IsNullOrEmpty(Culture); - public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); - public bool ShouldSerializeSkillTemplate() => !string.IsNullOrEmpty(SkillTemplate); - public bool ShouldSerializeIsHiddenEncyclopedia() => !string.IsNullOrEmpty(IsHiddenEncyclopedia); - public bool ShouldSerializeFace() => Face != null && Face.HasContent; - public bool ShouldSerializeSkills() => Skills != null && Skills.HasContent; - public bool ShouldSerializeTraits() => Traits != null && Traits.HasContent; - public bool ShouldSerializeUpgradeTargets() => UpgradeTargets != null && UpgradeTargets.HasContent; - public bool ShouldSerializeEquipments() => Equipments != null && Equipments.HasContent; -} - -public class NPCFace -{ - [XmlElement("face_key_template")] - public FaceKeyTemplate? FaceKeyTemplate { get; set; } - - [XmlIgnore] - public bool HasContent => FaceKeyTemplate != null && FaceKeyTemplate.HasContent; -} - -public class FaceKeyTemplate -{ - [XmlAttribute("value")] - public string? Value { get; set; } - - [XmlIgnore] - public bool HasContent => !string.IsNullOrEmpty(Value); - - public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); -} - -public class NpcCharacterSkills -{ - [XmlElement("skill")] - public List SkillList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => SkillList.Count > 0; -} - -public class Skill -{ - [XmlAttribute("id")] - public string? Id { get; set; } - - [XmlAttribute("value")] - public string? Value { get; set; } - - public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); - public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); -} - -public class NpcTraits -{ - [XmlIgnore] - public bool HasContent => false; -} - -public class NpcUpgradeTargets -{ - [XmlIgnore] - public bool HasContent => false; -} - -public class NpcEquipments -{ - [XmlElement("EquipmentRoster")] - public List EquipmentRosterList { get; set; } = new List(); - - [XmlElement("EquipmentSet")] - public List EquipmentSetList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => EquipmentRosterList.Count > 0 || EquipmentSetList.Count > 0; -} - -public class EquipmentRoster -{ - [XmlAttribute("civilian")] - public string? Civilian { get; set; } - - [XmlElement("equipment")] - public List EquipmentList { get; set; } = new List(); - - public bool ShouldSerializeCivilian() => !string.IsNullOrEmpty(Civilian); -} - -public class Equipment -{ - [XmlAttribute("slot")] - public string? Slot { get; set; } - - [XmlAttribute("id")] - public string? Id { get; set; } - - public bool ShouldSerializeSlot() => !string.IsNullOrEmpty(Slot); - public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); -} - -public class NpcEquipmentSet -{ - [XmlAttribute("id")] - public string? Id { get; set; } - - public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); -} diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs new file mode 100644 index 0000000..d003078 --- /dev/null +++ b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs @@ -0,0 +1,315 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace BannerlordModEditor.Common.Models.V1_3_15; + +public class NPCCharacter +{ + [XmlAttribute("id")] + public string Id { get; set; } = string.Empty; + + [XmlAttribute("default_group")] + public string? DefaultGroup { get; set; } + + [XmlAttribute("level")] + public string? Level { get; set; } + + [XmlAttribute("occupation")] + public string? Occupation { get; set; } + + [XmlAttribute("culture")] + public string? Culture { get; set; } + + [XmlAttribute("name")] + public string? Name { get; set; } + + [XmlAttribute("skill_template")] + public string? SkillTemplate { get; set; } + + [XmlAttribute("is_hidden_encyclopedia")] + public string? IsHiddenEncyclopedia { get; set; } + + [XmlAttribute("is_hero")] + public string? IsHero { get; set; } + + [XmlAttribute("is_mercenary")] + public string? IsMercenary { get; set; } + + [XmlElement("face")] + public NPCFace? Face { get; set; } + + [XmlElement("skills")] + public NpcCharacterSkills? Skills { get; set; } + + [XmlElement("Traits")] + public NpcTraits? Traits { get; set; } + + [XmlElement("upgrade_targets")] + public NpcUpgradeTargets? UpgradeTargets { get; set; } + + [XmlElement("Equipments")] + public NpcEquipments? Equipments { get; set; } + + public bool ShouldSerializeDefaultGroup() => !string.IsNullOrEmpty(DefaultGroup); + public bool ShouldSerializeLevel() => !string.IsNullOrEmpty(Level); + public bool ShouldSerializeOccupation() => !string.IsNullOrEmpty(Occupation); + public bool ShouldSerializeCulture() => !string.IsNullOrEmpty(Culture); + public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); + public bool ShouldSerializeSkillTemplate() => !string.IsNullOrEmpty(SkillTemplate); + public bool ShouldSerializeIsHiddenEncyclopedia() => !string.IsNullOrEmpty(IsHiddenEncyclopedia); + public bool ShouldSerializeIsHero() => !string.IsNullOrEmpty(IsHero); + public bool ShouldSerializeIsMercenary() => !string.IsNullOrEmpty(IsMercenary); + public bool ShouldSerializeFace() => Face != null && Face.HasContent; + public bool ShouldSerializeSkills() => Skills != null && Skills.HasContent; + public bool ShouldSerializeTraits() => Traits != null && Traits.HasContent; + public bool ShouldSerializeUpgradeTargets() => UpgradeTargets != null && UpgradeTargets.HasContent; + public bool ShouldSerializeEquipments() => Equipments != null && Equipments.HasContent; +} + +public class NPCFace +{ + [XmlElement("face_key_template")] + public FaceKeyTemplate? FaceKeyTemplate { get; set; } + + [XmlElement("BodyProperties")] + public BodyProperties? BodyProperties { get; set; } + + [XmlElement("BodyPropertiesMax")] + public BodyPropertiesMax? BodyPropertiesMax { get; set; } + + [XmlElement("hair_tags")] + public HairTags? HairTags { get; set; } + + [XmlElement("beard_tags")] + public BeardTags? BeardTags { get; set; } + + [XmlElement("tattoo_tags")] + public TattooTags? TattooTags { get; set; } + + [XmlIgnore] + public bool HasContent => (FaceKeyTemplate != null && FaceKeyTemplate.HasContent) + || (BodyProperties != null && BodyProperties.HasContent) + || (HairTags != null && HairTags.HasContent) + || (BeardTags != null && BeardTags.HasContent) + || (TattooTags != null && TattooTags.HasContent); +} + +public class FaceKeyTemplate +{ + [XmlAttribute("value")] + public string? Value { get; set; } + + [XmlIgnore] + public bool HasContent => !string.IsNullOrEmpty(Value); + + public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); +} + +public class BodyProperties +{ + [XmlAttribute("version")] + public string? Version { get; set; } + + [XmlAttribute("age")] + public string? Age { get; set; } + + [XmlAttribute("weight")] + public string? Weight { get; set; } + + [XmlAttribute("build")] + public string? Build { get; set; } + + [XmlAttribute("key")] + public string? Key { get; set; } + + [XmlIgnore] + public bool HasContent => !string.IsNullOrEmpty(Version) || !string.IsNullOrEmpty(Age) + || !string.IsNullOrEmpty(Weight) || !string.IsNullOrEmpty(Build) || !string.IsNullOrEmpty(Key); + + public bool ShouldSerializeVersion() => !string.IsNullOrEmpty(Version); + public bool ShouldSerializeAge() => !string.IsNullOrEmpty(Age); + public bool ShouldSerializeWeight() => !string.IsNullOrEmpty(Weight); + public bool ShouldSerializeBuild() => !string.IsNullOrEmpty(Build); + public bool ShouldSerializeKey() => !string.IsNullOrEmpty(Key); +} + +public class BodyPropertiesMax +{ + [XmlAttribute("version")] + public string? Version { get; set; } + + [XmlAttribute("age")] + public string? Age { get; set; } + + [XmlAttribute("weight")] + public string? Weight { get; set; } + + [XmlAttribute("build")] + public string? Build { get; set; } + + [XmlAttribute("key")] + public string? Key { get; set; } + + [XmlIgnore] + public bool HasContent => !string.IsNullOrEmpty(Version) || !string.IsNullOrEmpty(Age) + || !string.IsNullOrEmpty(Weight) || !string.IsNullOrEmpty(Build) || !string.IsNullOrEmpty(Key); + + public bool ShouldSerializeVersion() => !string.IsNullOrEmpty(Version); + public bool ShouldSerializeAge() => !string.IsNullOrEmpty(Age); + public bool ShouldSerializeWeight() => !string.IsNullOrEmpty(Weight); + public bool ShouldSerializeBuild() => !string.IsNullOrEmpty(Build); + public bool ShouldSerializeKey() => !string.IsNullOrEmpty(Key); +} + +public class HairTags +{ + [XmlElement("hair_tag")] + public List HairTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => HairTagList.Count > 0; + + public bool ShouldSerializeHairTagList() => HairTagList.Count > 0; +} + +public class HairTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } + + public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); +} + +public class BeardTags +{ + [XmlElement("beard_tag")] + public List BeardTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => BeardTagList.Count > 0; + + public bool ShouldSerializeBeardTagList() => BeardTagList.Count > 0; +} + +public class BeardTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } + + public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); +} + +public class TattooTags +{ + [XmlElement("tattoo_tag")] + public List TattooTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => TattooTagList.Count > 0; + + public bool ShouldSerializeTattooTagList() => TattooTagList.Count > 0; +} + +public class TattooTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } + + public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); +} + +public class NpcCharacterSkills +{ + [XmlElement("skill")] + public List SkillList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => SkillList.Count > 0; +} + +public class Skill +{ + [XmlAttribute("id")] + public string? Id { get; set; } + + [XmlAttribute("value")] + public string? Value { get; set; } + + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); +} + +public class NpcTraits +{ + [XmlIgnore] + public bool HasContent => false; +} + +public class NpcUpgradeTargets +{ + [XmlElement("upgrade_target")] + public List UpgradeTargetList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => UpgradeTargetList.Count > 0; + + public bool ShouldSerializeUpgradeTargetList() => UpgradeTargetList.Count > 0; +} + +public class UpgradeTarget +{ + [XmlAttribute("id")] + public string? Id { get; set; } + + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); +} + +public class NpcEquipments +{ + [XmlElement("EquipmentRoster")] + public List EquipmentRosterList { get; set; } = new List(); + + [XmlElement("EquipmentSet")] + public List EquipmentSetList { get; set; } = new List(); + + [XmlElement("equipment")] + public List EquipmentList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => EquipmentRosterList.Count > 0 || EquipmentSetList.Count > 0 || EquipmentList.Count > 0; +} + +public class EquipmentRoster +{ + [XmlAttribute("civilian")] + public string? Civilian { get; set; } + + [XmlElement("equipment")] + public List EquipmentList { get; set; } = new List(); + + public bool ShouldSerializeCivilian() => !string.IsNullOrEmpty(Civilian); +} + +public class Equipment +{ + [XmlAttribute("slot")] + public string? Slot { get; set; } + + [XmlAttribute("id")] + public string? Id { get; set; } + + public bool ShouldSerializeSlot() => !string.IsNullOrEmpty(Slot); + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); +} + +public class NpcEquipmentSet +{ + [XmlAttribute("id")] + public string? Id { get; set; } + + [XmlAttribute("civilian")] + public string? Civilian { get; set; } + + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeCivilian() => !string.IsNullOrEmpty(Civilian); +} From 89bc1189534c28379860bb5140fe7cd4c71b0b96 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:23:02 +0800 Subject: [PATCH 07/11] fix: NormalizeXml handles self-closing to open/close for all empty elements --- BannerlordModEditor.Common.Tests/XmlTestUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BannerlordModEditor.Common.Tests/XmlTestUtils.cs b/BannerlordModEditor.Common.Tests/XmlTestUtils.cs index 0b5c262..5496c90 100644 --- a/BannerlordModEditor.Common.Tests/XmlTestUtils.cs +++ b/BannerlordModEditor.Common.Tests/XmlTestUtils.cs @@ -228,10 +228,10 @@ public static string NormalizeXml(string xml, XmlComparisonOptions? options = nu } } - // 特殊处理:将自闭合标签转换为开始/结束标签格式以保持一致性 - foreach (var element in doc.Descendants().Where(e => e.IsEmpty && e.Name.LocalName == "base")) + // 特殊处理:将所有自闭合标签转换为开始/结束标签格式以保持一致性 + foreach (var element in doc.Descendants().Where(e => e.IsEmpty)) { - element.Add(""); // 添加空内容强制使用开始/结束标签 + element.Add(""); } return doc.ToString(); From 350250be5efea231c06163b610413207135f0305 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:30:19 +0800 Subject: [PATCH 08/11] fix: resolve build errors - duplicate UpgradeTarget and missing types in Lords.cs --- .../Models/V1_3_15/Lords.cs | 118 ++++++++++++++++++ .../Models/V1_3_15/SharedNPCCharacters.cs | 8 -- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/BannerlordModEditor.Common/Models/V1_3_15/Lords.cs b/BannerlordModEditor.Common/Models/V1_3_15/Lords.cs index 062a56f..83d1054 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/Lords.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/Lords.cs @@ -123,3 +123,121 @@ public class UpgradeTarget [XmlAttribute("id")] public string Id { get; set; } = string.Empty; } + +public class FaceMesh +{ + [XmlElement("BodyProperties")] + public BodyProperties? BodyProperties { get; set; } + + [XmlElement("hair_tags")] + public HairTags? HairTags { get; set; } + + [XmlElement("beard_tags")] + public BeardTags? BeardTags { get; set; } + + [XmlElement("tattoo_tags")] + public TattooTags? TattooTags { get; set; } + + [XmlIgnore] + public bool HasContent => (BodyProperties != null && BodyProperties.HasContent) + || (HairTags != null && HairTags.HasContent) + || (BeardTags != null && BeardTags.HasContent) + || (TattooTags != null && TattooTags.HasContent); +} + +public class BodyProperties +{ + [XmlAttribute("version")] + public string? Version { get; set; } + + [XmlAttribute("age")] + public string? Age { get; set; } + + [XmlAttribute("weight")] + public string? Weight { get; set; } + + [XmlAttribute("build")] + public string? Build { get; set; } + + [XmlAttribute("key")] + public string? Key { get; set; } + + [XmlIgnore] + public bool HasContent => !string.IsNullOrEmpty(Version) || !string.IsNullOrEmpty(Age) + || !string.IsNullOrEmpty(Weight) || !string.IsNullOrEmpty(Build) || !string.IsNullOrEmpty(Key); + + public bool ShouldSerializeVersion() => !string.IsNullOrEmpty(Version); + public bool ShouldSerializeAge() => !string.IsNullOrEmpty(Age); + public bool ShouldSerializeWeight() => !string.IsNullOrEmpty(Weight); + public bool ShouldSerializeBuild() => !string.IsNullOrEmpty(Build); + public bool ShouldSerializeKey() => !string.IsNullOrEmpty(Key); +} + +public class HairTags +{ + [XmlElement("hair_tag")] + public List HairTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => HairTagList.Count > 0; +} + +public class HairTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } +} + +public class BeardTags +{ + [XmlElement("beard_tag")] + public List BeardTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => BeardTagList.Count > 0; +} + +public class BeardTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } +} + +public class TattooTags +{ + [XmlElement("tattoo_tag")] + public List TattooTagList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => TattooTagList.Count > 0; +} + +public class TattooTag +{ + [XmlAttribute("name")] + public string? Name { get; set; } +} + +public class Skills +{ + [XmlElement("skill")] + public List SkillList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => SkillList.Count > 0; +} + +public class Skill +{ + [XmlAttribute("id")] + public string? Id { get; set; } + + [XmlAttribute("value")] + public string? Value { get; set; } +} + +public class EquipmentSet +{ + [XmlIgnore] + public bool HasContent => false; +} diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs index d003078..fbeac52 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs @@ -256,14 +256,6 @@ public class NpcUpgradeTargets public bool ShouldSerializeUpgradeTargetList() => UpgradeTargetList.Count > 0; } -public class UpgradeTarget -{ - [XmlAttribute("id")] - public string? Id { get; set; } - - public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); -} - public class NpcEquipments { [XmlElement("EquipmentRoster")] From 9086c8508896d6b9f719e98893a27d6751d3770e Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:38:34 +0800 Subject: [PATCH 09/11] fix: remove duplicate class definitions from SharedNPCCharacters.cs (now shared with Lords.cs) --- .../Models/V1_3_15/SharedNPCCharacters.cs | 106 ------------------ 1 file changed, 106 deletions(-) diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs index fbeac52..f7ed3e1 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs @@ -105,34 +105,6 @@ public class FaceKeyTemplate public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); } -public class BodyProperties -{ - [XmlAttribute("version")] - public string? Version { get; set; } - - [XmlAttribute("age")] - public string? Age { get; set; } - - [XmlAttribute("weight")] - public string? Weight { get; set; } - - [XmlAttribute("build")] - public string? Build { get; set; } - - [XmlAttribute("key")] - public string? Key { get; set; } - - [XmlIgnore] - public bool HasContent => !string.IsNullOrEmpty(Version) || !string.IsNullOrEmpty(Age) - || !string.IsNullOrEmpty(Weight) || !string.IsNullOrEmpty(Build) || !string.IsNullOrEmpty(Key); - - public bool ShouldSerializeVersion() => !string.IsNullOrEmpty(Version); - public bool ShouldSerializeAge() => !string.IsNullOrEmpty(Age); - public bool ShouldSerializeWeight() => !string.IsNullOrEmpty(Weight); - public bool ShouldSerializeBuild() => !string.IsNullOrEmpty(Build); - public bool ShouldSerializeKey() => !string.IsNullOrEmpty(Key); -} - public class BodyPropertiesMax { [XmlAttribute("version")] @@ -161,84 +133,6 @@ public class BodyPropertiesMax public bool ShouldSerializeKey() => !string.IsNullOrEmpty(Key); } -public class HairTags -{ - [XmlElement("hair_tag")] - public List HairTagList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => HairTagList.Count > 0; - - public bool ShouldSerializeHairTagList() => HairTagList.Count > 0; -} - -public class HairTag -{ - [XmlAttribute("name")] - public string? Name { get; set; } - - public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); -} - -public class BeardTags -{ - [XmlElement("beard_tag")] - public List BeardTagList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => BeardTagList.Count > 0; - - public bool ShouldSerializeBeardTagList() => BeardTagList.Count > 0; -} - -public class BeardTag -{ - [XmlAttribute("name")] - public string? Name { get; set; } - - public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); -} - -public class TattooTags -{ - [XmlElement("tattoo_tag")] - public List TattooTagList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => TattooTagList.Count > 0; - - public bool ShouldSerializeTattooTagList() => TattooTagList.Count > 0; -} - -public class TattooTag -{ - [XmlAttribute("name")] - public string? Name { get; set; } - - public bool ShouldSerializeName() => !string.IsNullOrEmpty(Name); -} - -public class NpcCharacterSkills -{ - [XmlElement("skill")] - public List SkillList { get; set; } = new List(); - - [XmlIgnore] - public bool HasContent => SkillList.Count > 0; -} - -public class Skill -{ - [XmlAttribute("id")] - public string? Id { get; set; } - - [XmlAttribute("value")] - public string? Value { get; set; } - - public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); - public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); -} - public class NpcTraits { [XmlIgnore] From d39fe87379164cb1daed9cab43f596f4506c0e2f Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:46:45 +0800 Subject: [PATCH 10/11] fix: nest Skill class inside NpcCharacterSkills to avoid conflict with Lords.Skill --- .../Models/V1_3_15/SharedNPCCharacters.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs index f7ed3e1..92cb167 100644 --- a/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs +++ b/BannerlordModEditor.Common/Models/V1_3_15/SharedNPCCharacters.cs @@ -139,6 +139,27 @@ public class NpcTraits public bool HasContent => false; } +public class NpcCharacterSkills +{ + [XmlElement("skill")] + public List SkillList { get; set; } = new List(); + + [XmlIgnore] + public bool HasContent => SkillList.Count > 0; + + public class Skill + { + [XmlAttribute("id")] + public string? Id { get; set; } + + [XmlAttribute("value")] + public string? Value { get; set; } + + public bool ShouldSerializeId() => !string.IsNullOrEmpty(Id); + public bool ShouldSerializeValue() => !string.IsNullOrEmpty(Value); + } +} + public class NpcUpgradeTargets { [XmlElement("upgrade_target")] From 3e75ee1533b2814ed632bf4352f791999be81358 Mon Sep 17 00:00:00 2001 From: OpenCode Bot Date: Sun, 22 Mar 2026 15:53:17 +0800 Subject: [PATCH 11/11] fix: update test class references to match renamed models (Characters->Heroes, BanditFactions->Bandits) --- .../Models/V1_3_15/BanditsRoundtripTests.cs | 2 +- .../Models/V1_3_15/HeroesRoundtripTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs index c17bc89..4e418c4 100644 --- a/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/BanditsRoundtripTests.cs @@ -12,7 +12,7 @@ public async Task Bandits_Roundtrip_ShouldPreserveData() { var xmlPath = Path.Combine("TestData", "V1_3_15", "bandits.xml"); var originalXml = await File.ReadAllTextAsync(xmlPath); - var obj = XmlTestUtils.Deserialize(originalXml); + var obj = XmlTestUtils.Deserialize(originalXml); var serialized = XmlTestUtils.Serialize(obj, originalXml); Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); } diff --git a/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs b/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs index 7952d09..401859b 100644 --- a/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs +++ b/BannerlordModEditor.Common.Tests/Models/V1_3_15/HeroesRoundtripTests.cs @@ -12,7 +12,7 @@ public async Task Heroes_Roundtrip_ShouldPreserveData() { var xmlPath = Path.Combine("TestData", "V1_3_15", "heroes.xml"); var originalXml = await File.ReadAllTextAsync(xmlPath); - var obj = XmlTestUtils.Deserialize(originalXml); + var obj = XmlTestUtils.Deserialize(originalXml); var serialized = XmlTestUtils.Serialize(obj, originalXml); Assert.True(XmlTestUtils.AreStructurallyEqual(originalXml, serialized)); }