diff --git a/src/Persistence/BasicModel/GameConfiguration.Generated.cs b/src/Persistence/BasicModel/GameConfiguration.Generated.cs
index 9460cd4..9562054 100644
--- a/src/Persistence/BasicModel/GameConfiguration.Generated.cs
+++ b/src/Persistence/BasicModel/GameConfiguration.Generated.cs
@@ -484,6 +484,24 @@ public partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuration.
set => base.DuelConfiguration = value;
}
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("castleSiegeConfiguration")]
+ public CastleSiegeConfiguration RawCastleSiegeConfiguration
+ {
+ get => base.CastleSiegeConfiguration as CastleSiegeConfiguration;
+ set => base.CastleSiegeConfiguration = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration
+ {
+ get => base.CastleSiegeConfiguration;
+ set => base.CastleSiegeConfiguration = value;
+ }
+
///
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
diff --git a/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj b/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj
index 4d32df0..1711a6d 100644
--- a/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj
+++ b/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj
@@ -48,8 +48,15 @@
+
-
+
diff --git a/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs b/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs
index cd714d0..8962b54 100644
--- a/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs
+++ b/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs
@@ -27,6 +27,9 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -41,6 +44,11 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -118,6 +126,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Entity().HasMany(entity => entity.RawCharacters).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawEquippedItems).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawNpcStates).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawLetters).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawLearnedSkills).WithOne().OnDelete(DeleteBehavior.Cascade);
@@ -131,6 +140,17 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Entity().HasOne(entity => entity.RawLeftGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasOne(entity => entity.RawRightGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasOne(entity => entity.RawMagicEffectDefinition).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawStateSchedule).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawNpcDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawGateDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawGateLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawStatueDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawStatueLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawStatueRegenUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawAttackMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasMany(entity => entity.RawDefenseMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasOne(entity => entity.RawDefenseRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasOne(entity => entity.RawAttackRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawStatAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawAttributeCombinations).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
@@ -159,6 +179,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Entity().HasMany(entity => entity.RawGlobalBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawPlugInConfigurations).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawMiniGameDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity().HasOne(entity => entity.RawCastleSiegeConfiguration).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawMonsterSpawns).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasMany(entity => entity.RawEnterGates).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity().HasOne(entity => entity.RawBattleZone).WithOne().OnDelete(DeleteBehavior.Cascade);
diff --git a/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs b/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs
index ec2d587..525e1eb 100644
--- a/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs
+++ b/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs
@@ -243,6 +243,32 @@ internal partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuratio
}
}
+ ///
+ /// Gets or sets the identifier of .
+ ///
+ public Guid? CastleSiegeConfigurationId { get; set; }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [ForeignKey(nameof(CastleSiegeConfigurationId))]
+ public CastleSiegeConfiguration RawCastleSiegeConfiguration
+ {
+ get => base.CastleSiegeConfiguration as CastleSiegeConfiguration;
+ set => base.CastleSiegeConfiguration = value;
+ }
+
+ ///
+ [NotMapped]
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration
+ {
+ get => base.CastleSiegeConfiguration;set
+ {
+ base.CastleSiegeConfiguration = value;
+ this.CastleSiegeConfigurationId = this.RawCastleSiegeConfiguration?.Id;
+ }
+ }
+
///
public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
diff --git a/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs b/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs
index 589e0a3..49d3cb3 100644
--- a/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs
+++ b/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs
@@ -44,6 +44,15 @@ public static class MapsterConfigurator
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
.Include();
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
.Include();
@@ -86,6 +95,21 @@ public static class MapsterConfigurator
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
.Include();
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
+ Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
+ .Include();
+
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig()
.Include();
diff --git a/src/Persistence/MUnique.OpenMU.Persistence.csproj b/src/Persistence/MUnique.OpenMU.Persistence.csproj
index 3763d8f..2a2d4d2 100644
--- a/src/Persistence/MUnique.OpenMU.Persistence.csproj
+++ b/src/Persistence/MUnique.OpenMU.Persistence.csproj
@@ -41,8 +41,15 @@
+
-
+
diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs
index 06f67a4..d447393 100644
--- a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs
+++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs
@@ -3904,6 +3904,24 @@ public class PacketStructureTests
"Packet length mismatch: declared length does not match calculated size");
}
+ ///
+ /// Tests the packet size calculation for HeykelSavasiTeamSelect.
+ ///
+ [Test]
+ public void HeykelSavasiTeamSelect_PacketSizeValidation()
+ {
+ // Fixed-length packet validation
+ const int expectedLength = 4;
+ var actualLength = HeykelSavasiTeamSelectRef.Length;
+
+ Assert.That(actualLength, Is.EqualTo(expectedLength),
+ "Packet length mismatch: declared length does not match calculated size");
+
+ // Validate field 'Team' boundary
+ Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'Team' exceeds packet boundary");
+ }
+
///
/// Tests the packet size calculation for ChatCommandListRequest.
///
diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs
index 42a5689..31b3147 100644
--- a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs
+++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs
@@ -6415,10 +6415,102 @@ public class PacketStructureTests
}
///
- /// Tests the packet size calculation for ChatCommandInfo.
+ /// Tests the packet size calculation for HeykelSavasiOpenTeamPanel.
///
[Test]
- public void ChatCommandInfo_PacketSizeValidation()
+ public void HeykelSavasiOpenTeamPanel_PacketSizeValidation()
+ {
+ // Fixed-length packet validation
+ const int expectedLength = 5;
+ var actualLength = HeykelSavasiOpenTeamPanelRef.Length;
+
+ Assert.That(actualLength, Is.EqualTo(expectedLength),
+ "Packet length mismatch: declared length does not match calculated size");
+
+ // Validate field 'RedCount' boundary
+ Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'RedCount' exceeds packet boundary");
+
+ // Validate field 'BlueCount' boundary
+ Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'BlueCount' exceeds packet boundary");
+ }
+
+ ///
+ /// Tests the packet size calculation for HeykelSavasiHudState.
+ ///
+ [Test]
+ public void HeykelSavasiHudState_PacketSizeValidation()
+ {
+ // Fixed-length packet validation
+ const int expectedLength = 11;
+ var actualLength = HeykelSavasiHudStateRef.Length;
+
+ Assert.That(actualLength, Is.EqualTo(expectedLength),
+ "Packet length mismatch: declared length does not match calculated size");
+
+ // Validate field 'Phase' boundary
+ Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'Phase' exceeds packet boundary");
+
+ // Validate field 'MyTeam' boundary
+ Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'MyTeam' exceeds packet boundary");
+
+ // Validate field 'RedCount' boundary
+ Assert.That(5 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'RedCount' exceeds packet boundary");
+
+ // Validate field 'BlueCount' boundary
+ Assert.That(6 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'BlueCount' exceeds packet boundary");
+
+ // Validate field 'RedProgress' boundary
+ Assert.That(7 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'RedProgress' exceeds packet boundary");
+
+ // Validate field 'BlueProgress' boundary
+ Assert.That(8 + 1, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'BlueProgress' exceeds packet boundary");
+
+ // Validate field 'RemainingSeconds' boundary
+ Assert.That(9 + 2, Is.LessThanOrEqualTo(expectedLength),
+ "Field 'RemainingSeconds' exceeds packet boundary");
+ }
+
+ ///
+ /// Tests the packet size calculation for HeykelSavasiTeamRoster.
+ ///
+ [Test]
+ public void HeykelSavasiTeamRoster_PacketSizeValidation()
+ {
+ // Basic packet validation
+ // Validate header type and field boundaries
+
+ // Field 'Count' starts at index 3 with size 1
+ Assert.That(3, Is.GreaterThanOrEqualTo(0),
+ "Field 'Count' has invalid negative index");
+ }
+
+ ///
+ /// Tests the packet size calculation for HeykelSavasiScoreboard.
+ ///
+ [Test]
+ public void HeykelSavasiScoreboard_PacketSizeValidation()
+ {
+ // Basic packet validation
+ // Validate header type and field boundaries
+
+ // Field 'Count' starts at index 3 with size 1
+ Assert.That(3, Is.GreaterThanOrEqualTo(0),
+ "Field 'Count' has invalid negative index");
+ }
+
+ ///
+ /// Tests the packet size calculation for AvailableChatCommand.
+ ///
+ [Test]
+ public void AvailableChatCommand_PacketSizeValidation()
{
// Basic packet validation
// Validate header type and field boundaries
diff --git a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs
new file mode 100644
index 0000000..e16b7b1
--- /dev/null
+++ b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs
@@ -0,0 +1,56 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Tests;
+
+using Microsoft.Extensions.Logging.Abstractions;
+using MUnique.OpenMU.Persistence.EntityFramework;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// Tests that the typed contexts can build their entity model. A typed context keeps only the edited type
+/// (plus its aggregate) and ignores every other type, so a type which is only mapped in the full context
+/// slips through the build and blows up at runtime instead. The startup reads the plugin configurations
+/// through such a context before anything else, so a broken model there means the server doesn't start.
+/// No database is needed: building the model already runs the EF model validation.
+///
+[TestFixture]
+internal class TypedContextModelTests
+{
+ ///
+ /// Builds the model of the typed context which the startup uses to read the plugin configurations.
+ /// A failing connection is fine here (there may be no database); a failing model is not.
+ ///
+ [Test]
+ public void PlugInConfigurationContextBuildsModel()
+ {
+ var provider = new PersistenceContextProvider(new NullLoggerFactory(), null);
+ using var context = provider.CreateNewTypedContext(typeof(PlugInConfiguration), false);
+
+ try
+ {
+ _ = context.GetAsync().AsTask().GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ AssertNoModelError(ex);
+ }
+ }
+
+ private static void AssertNoModelError(Exception exception)
+ {
+ for (var ex = exception; ex is not null; ex = ex.InnerException!)
+ {
+ if (ex is InvalidOperationException && ex.Message.Contains("requires a primary key"))
+ {
+ Assert.Fail($"The entity model of the typed context is broken: {ex.Message}");
+ }
+
+ if (ex.InnerException is null)
+ {
+ break;
+ }
+ }
+ }
+}