diff --git a/src/DataModel/Configuration/CastleSiegeConfiguration.cs b/src/DataModel/Configuration/CastleSiegeConfiguration.cs
new file mode 100644
index 0000000..595d673
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeConfiguration.cs
@@ -0,0 +1,152 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+using MUnique.OpenMU.Annotations;
+using MUnique.OpenMU.DataModel.Configuration.Items;
+
+///
+/// Main configuration for the castle siege event.
+///
+[Cloneable]
+public partial class CastleSiegeConfiguration
+{
+ ///
+ /// Gets or sets a value indicating whether the castle siege feature is enabled.
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// Gets or sets the number of seconds a guild must hold the crown to capture the castle.
+ ///
+ public int CrownHoldTimeSeconds { get; set; } = 30;
+
+ ///
+ /// Gets or sets the minimum combined level of a guild master required to register for the siege.
+ ///
+ public int RegisterMinLevel { get; set; } = 200;
+
+ ///
+ /// Gets or sets the minimum number of guild members required to register for the siege.
+ ///
+ public int RegisterMinMembers { get; set; } = 20;
+
+ ///
+ /// Gets or sets the minimum number of seconds a participant must be present in the battle to be eligible for a reward.
+ ///
+ public int ParticipantRewardMinSeconds { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of attacking alliance slots.
+ ///
+ public int MaxAttackingGuilds { get; set; } = 3;
+
+ ///
+ /// Gets or sets the guild score awarded to the guild that wins the siege.
+ ///
+ public int GuildScoreCastleSiege { get; set; }
+
+ ///
+ /// Gets or sets the guild score awarded to alliance member guilds of the winning side.
+ ///
+ public int GuildScoreCastleSiegeMembers { get; set; }
+
+ ///
+ /// Gets or sets the Zen cost for the castle owner to re-purchase a destroyed gate.
+ ///
+ public int GateBuyPrice { get; set; }
+
+ ///
+ /// Gets or sets the Zen cost for the castle owner to re-purchase a destroyed statue.
+ ///
+ public int StatueBuyPrice { get; set; }
+
+ ///
+ /// Gets or sets the map definition for the Valley of Loren (map 30), where the siege takes place.
+ ///
+ public virtual GameMapDefinition? CastleSiegeMapDefinition { get; set; }
+
+ ///
+ /// Gets or sets the map definition for the Land of Trials (map 31), the castle-owner's exclusive zone.
+ ///
+ public virtual GameMapDefinition? LandOfTrialsMapDefinition { get; set; }
+
+ ///
+ /// Gets or sets the item definition for the participation reward item.
+ ///
+ public virtual ItemDefinition? RewardItemDefinition { get; set; }
+
+ ///
+ /// Gets or sets the schedule entries that define when each siege state begins.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection StateSchedule { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the definitions for all castle siege NPCs (gates, statues, etc.).
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection NpcDefinitions { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the upgrade levels for gate defense.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection GateDefenseUpgrades { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the upgrade levels for gate maximum HP.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection GateLifeUpgrades { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the upgrade levels for statue defense.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection StatueDefenseUpgrades { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the upgrade levels for statue maximum HP.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection StatueLifeUpgrades { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the upgrade levels for statue HP regeneration.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection StatueRegenUpgrades { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the zones on the siege map where attacking siege machines may be placed.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection AttackMachineZones { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the zones on the siege map where defensive siege machines may be placed.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection DefenseMachineZones { get; protected set; } = null!;
+
+ ///
+ /// Gets or sets the zone where defending players respawn during the siege.
+ ///
+ [MemberOfAggregate]
+ public virtual CastleSiegeZoneDefinition? DefenseRespawnArea { get; set; }
+
+ ///
+ /// Gets or sets the zone where attacking players respawn during the siege.
+ ///
+ [MemberOfAggregate]
+ public virtual CastleSiegeZoneDefinition? AttackRespawnArea { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return "Castle Siege Configuration";
+ }
+}
diff --git a/src/DataModel/Configuration/CastleSiegeJoinSide.cs b/src/DataModel/Configuration/CastleSiegeJoinSide.cs
new file mode 100644
index 0000000..bae4d83
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeJoinSide.cs
@@ -0,0 +1,36 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// Defines the side (defending or attacking) a guild or NPC belongs to in the castle siege.
+///
+public enum CastleSiegeJoinSide : byte
+{
+ ///
+ /// No side assigned.
+ ///
+ None = 0,
+
+ ///
+ /// The defending guild side.
+ ///
+ Defense = 1,
+
+ ///
+ /// The first attacking alliance slot.
+ ///
+ Attack1 = 2,
+
+ ///
+ /// The second attacking alliance slot.
+ ///
+ Attack2 = 3,
+
+ ///
+ /// The third attacking alliance slot.
+ ///
+ Attack3 = 4,
+}
diff --git a/src/DataModel/Configuration/CastleSiegeNpcDefinition.cs b/src/DataModel/Configuration/CastleSiegeNpcDefinition.cs
new file mode 100644
index 0000000..c1d6d78
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeNpcDefinition.cs
@@ -0,0 +1,55 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+using MUnique.OpenMU.Annotations;
+
+///
+/// Defines a castle siege NPC instance, including its spawn location, side, and persistence settings.
+///
+[Cloneable]
+public partial class CastleSiegeNpcDefinition
+{
+ ///
+ /// Gets or sets the monster definition template for this NPC.
+ ///
+ public virtual MonsterDefinition? MonsterDefinition { get; set; }
+
+ ///
+ /// Gets or sets the unique instance identifier within its NPC type.
+ ///
+ public byte InstanceId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this NPC's state is persisted to the database between sieges.
+ ///
+ public bool IsPersistedToDatabase { get; set; }
+
+ ///
+ /// Gets or sets the default join side this NPC belongs to.
+ ///
+ public CastleSiegeJoinSide DefaultSide { get; set; }
+
+ ///
+ /// Gets or sets the X coordinate of the NPC's spawn position.
+ ///
+ public byte SpawnX { get; set; }
+
+ ///
+ /// Gets or sets the Y coordinate of the NPC's spawn position.
+ ///
+ public byte SpawnY { get; set; }
+
+ ///
+ /// Gets or sets the facing direction of the NPC at spawn.
+ ///
+ public Direction Direction { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.MonsterDefinition} #{this.InstanceId} at ({this.SpawnX},{this.SpawnY})";
+ }
+}
diff --git a/src/DataModel/Configuration/CastleSiegeState.cs b/src/DataModel/Configuration/CastleSiegeState.cs
new file mode 100644
index 0000000..82b5a24
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeState.cs
@@ -0,0 +1,61 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The state of the castle siege event cycle.
+///
+public enum CastleSiegeState : byte
+{
+ ///
+ /// Idle state before guild registration opens.
+ ///
+ Idle1 = 0,
+
+ ///
+ /// Guilds may register for the siege.
+ ///
+ RegisterGuild = 1,
+
+ ///
+ /// Idle state after guild registration.
+ ///
+ Idle2 = 2,
+
+ ///
+ /// Guilds may register emblems (Marks of Lord) to determine the attacking guilds.
+ ///
+ RegisterMark = 3,
+
+ ///
+ /// Idle state after mark registration.
+ ///
+ Idle3 = 4,
+
+ ///
+ /// Players are notified that the siege is about to start.
+ ///
+ Notify = 5,
+
+ ///
+ /// The siege map is prepared and entry is allowed.
+ ///
+ Ready = 6,
+
+ ///
+ /// The siege battle is in progress.
+ ///
+ Start = 7,
+
+ ///
+ /// The siege battle has ended and results are being processed.
+ ///
+ End = 8,
+
+ ///
+ /// The full siege cycle has completed.
+ ///
+ EndCycle = 9,
+}
diff --git a/src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs b/src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs
new file mode 100644
index 0000000..3065f40
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+using MUnique.OpenMU.Annotations;
+
+///
+/// Defines a scheduled transition to a specific at a given day and time.
+///
+[Cloneable]
+public partial class CastleSiegeStateScheduleEntry
+{
+ ///
+ /// Gets or sets the siege state that becomes active at the scheduled time.
+ ///
+ public CastleSiegeState State { get; set; }
+
+ ///
+ /// Gets or sets the day of the week on which this state transition occurs.
+ ///
+ public DayOfWeek DayOfWeek { get; set; }
+
+ ///
+ /// Gets or sets the hour (0–23) at which this state transition occurs.
+ ///
+ public byte Hour { get; set; }
+
+ ///
+ /// Gets or sets the minute (0–59) at which this state transition occurs.
+ ///
+ public byte Minute { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.State} on {this.DayOfWeek} at {this.Hour:D2}:{this.Minute:D2}";
+ }
+}
diff --git a/src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs b/src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs
new file mode 100644
index 0000000..dd372c1
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+using MUnique.OpenMU.Annotations;
+
+///
+/// Defines one level of an upgrade that the castle owner can apply to a gate or statue NPC.
+///
+[Cloneable]
+public partial class CastleSiegeUpgradeDefinition
+{
+ ///
+ /// Gets or sets the upgrade level (0–3), where 0 represents the base/unupgraded state.
+ ///
+ public byte Level { get; set; }
+
+ ///
+ /// Gets or sets the number of Jewels of Guardian required to perform this upgrade.
+ ///
+ public int RequiredJewelOfGuardianCount { get; set; }
+
+ ///
+ /// Gets or sets the amount of Zen required to perform this upgrade.
+ ///
+ public int RequiredZen { get; set; }
+
+ ///
+ /// Gets or sets the resulting stat value granted by this upgrade level (defense or max HP).
+ ///
+ public int Value { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"Level {this.Level}: Value={this.Value}, Jewels={this.RequiredJewelOfGuardianCount}, Zen={this.RequiredZen}";
+ }
+}
diff --git a/src/DataModel/Configuration/CastleSiegeUpgradeType.cs b/src/DataModel/Configuration/CastleSiegeUpgradeType.cs
new file mode 100644
index 0000000..25fa16e
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeUpgradeType.cs
@@ -0,0 +1,31 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+///
+/// The type of upgrade applied to a castle siege NPC (gate or statue).
+///
+public enum CastleSiegeUpgradeType : byte
+{
+ ///
+ /// No upgrade type assigned.
+ ///
+ Undefined = 0,
+
+ ///
+ /// Increases the defense stat of the NPC.
+ ///
+ Defense = 1,
+
+ ///
+ /// Increases the HP regeneration rate of the NPC.
+ ///
+ Regen = 2,
+
+ ///
+ /// Increases the maximum HP of the NPC.
+ ///
+ Life = 3,
+}
diff --git a/src/DataModel/Configuration/CastleSiegeZoneDefinition.cs b/src/DataModel/Configuration/CastleSiegeZoneDefinition.cs
new file mode 100644
index 0000000..602042b
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeZoneDefinition.cs
@@ -0,0 +1,40 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Configuration;
+
+using MUnique.OpenMU.Annotations;
+
+///
+/// Defines a rectangular zone on the castle siege map, used for spawn areas and machine zones.
+///
+[Cloneable]
+public partial class CastleSiegeZoneDefinition
+{
+ ///
+ /// Gets or sets the top-left X coordinate of the zone.
+ ///
+ public byte X1 { get; set; }
+
+ ///
+ /// Gets or sets the top-left Y coordinate of the zone.
+ ///
+ public byte Y1 { get; set; }
+
+ ///
+ /// Gets or sets the bottom-right X coordinate of the zone.
+ ///
+ public byte X2 { get; set; }
+
+ ///
+ /// Gets or sets the bottom-right Y coordinate of the zone.
+ ///
+ public byte Y2 { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.X1} / {this.Y1} to {this.X2} / {this.Y2}";
+ }
+}
diff --git a/src/DataModel/Configuration/GameConfiguration.cs b/src/DataModel/Configuration/GameConfiguration.cs
index 564e181..9f6941a 100644
--- a/src/DataModel/Configuration/GameConfiguration.cs
+++ b/src/DataModel/Configuration/GameConfiguration.cs
@@ -300,6 +300,12 @@ public partial class GameConfiguration
[MemberOfAggregate]
public virtual ICollection MiniGameDefinitions { get; protected set; } = null!;
+ ///
+ /// Gets or sets the castle siege configuration.
+ ///
+ [MemberOfAggregate]
+ public virtual CastleSiegeConfiguration? CastleSiegeConfiguration { get; set; }
+
///
public override string ToString()
{
diff --git a/src/DataModel/Entities/CastleSiegeData.cs b/src/DataModel/Entities/CastleSiegeData.cs
new file mode 100644
index 0000000..5067fcd
--- /dev/null
+++ b/src/DataModel/Entities/CastleSiegeData.cs
@@ -0,0 +1,67 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Entities;
+
+///
+/// Persistent state of the castle siege, stored as a single row across siege cycles.
+///
+[AggregateRoot]
+public class CastleSiegeData
+{
+ ///
+ /// Gets or sets the unique identifier of this record.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets or sets the persistent identifier of the guild that currently owns the castle.
+ /// when no guild owns the castle.
+ ///
+ public Guid? OwnerGuildId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether any guild currently occupies the castle.
+ ///
+ public bool IsOccupied { get; set; }
+
+ ///
+ /// Gets or sets the Chaos Machine tax rate applied by the castle owner (0–3).
+ ///
+ public byte TaxChaos { get; set; }
+
+ ///
+ /// Gets or sets the personal store tax rate applied by the castle owner (0–3).
+ ///
+ public byte TaxStore { get; set; }
+
+ ///
+ /// Gets or sets the entry fee (in Zen) for the castle owner's hunt zone (0–300000).
+ ///
+ public int TaxHunt { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the hunt zone (Land of Trials) is currently open to the public.
+ ///
+ public bool IsHuntZoneEnabled { get; set; }
+
+ ///
+ /// Gets or sets the accumulated tribute money collected from the hunt zone and taxes.
+ ///
+ public long TributeMoney { get; set; }
+
+ ///
+ /// Gets or sets the persisted states of all castle NPCs.
+ ///
+ [MemberOfAggregate]
+ public virtual ICollection NpcStates { get; protected set; } = null!;
+
+ ///
+ public override string ToString()
+ {
+ return this.IsOccupied
+ ? $"Castle owned by guild {this.OwnerGuildId}"
+ : "Castle unoccupied";
+ }
+}
diff --git a/src/DataModel/Entities/CastleSiegeGuildRegistration.cs b/src/DataModel/Entities/CastleSiegeGuildRegistration.cs
new file mode 100644
index 0000000..39119ae
--- /dev/null
+++ b/src/DataModel/Entities/CastleSiegeGuildRegistration.cs
@@ -0,0 +1,44 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Entities;
+
+///
+/// Stores a guild's registration data for the current castle siege cycle,
+/// including the number of emblems submitted to determine attacking guilds.
+///
+[AggregateRoot]
+public class CastleSiegeGuildRegistration
+{
+ ///
+ /// Gets or sets the unique identifier of this registration record.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets or sets the persistent identifier of the registered guild.
+ ///
+ public Guid GuildId { get; set; }
+
+ ///
+ /// Gets or sets the guild name, denormalized for convenience to avoid extra lookups during siege processing.
+ ///
+ public string GuildName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the number of Emblems of Lord registered by this guild.
+ ///
+ public int Marks { get; set; }
+
+ ///
+ /// Gets or sets the insertion order of this registration, used for tie-breaking when guilds have equal marks.
+ ///
+ public int RegistrationOrder { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.GuildName} (Marks={this.Marks}, Order={this.RegistrationOrder})";
+ }
+}
diff --git a/src/DataModel/Entities/CastleSiegeNpcState.cs b/src/DataModel/Entities/CastleSiegeNpcState.cs
new file mode 100644
index 0000000..509a0d8
--- /dev/null
+++ b/src/DataModel/Entities/CastleSiegeNpcState.cs
@@ -0,0 +1,52 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.DataModel.Entities;
+
+///
+/// Persistent state of a single castle siege NPC between siege cycles.
+///
+public class CastleSiegeNpcState
+{
+ ///
+ /// Gets or sets the unique identifier of this NPC state.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets or sets the monster definition number that identifies the NPC template.
+ ///
+ public short MonsterNumber { get; set; }
+
+ ///
+ /// Gets or sets the instance identifier matching .
+ ///
+ public byte InstanceId { get; set; }
+
+ ///
+ /// Gets or sets the current defense upgrade level (0–3).
+ ///
+ public byte DefenseLevel { get; set; }
+
+ ///
+ /// Gets or sets the current HP regeneration upgrade level (0–3).
+ ///
+ public byte RegenLevel { get; set; }
+
+ ///
+ /// Gets or sets the current maximum HP upgrade level (0–3).
+ ///
+ public byte LifeLevel { get; set; }
+
+ ///
+ /// Gets or sets the current HP of the NPC. A value of 0 means the NPC is destroyed.
+ ///
+ public int CurrentHp { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return $"NPC {this.MonsterNumber} #{this.InstanceId} (HP={this.CurrentHp})";
+ }
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs b/src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs
new file mode 100644
index 0000000..54c37fd
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs
@@ -0,0 +1,342 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeConfiguration : MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration, IIdentifiable, IConvertibleTo
+{
+
+ ///
+ /// Gets or sets the identifier of this instance.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("stateSchedule")]
+ public ICollection RawStateSchedule { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection StateSchedule
+ {
+ get => base.StateSchedule ??= new CollectionAdapter(this.RawStateSchedule);
+ protected set
+ {
+ this.StateSchedule.Clear();
+ foreach (var item in value)
+ {
+ this.StateSchedule.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("npcDefinitions")]
+ public ICollection RawNpcDefinitions { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection NpcDefinitions
+ {
+ get => base.NpcDefinitions ??= new CollectionAdapter(this.RawNpcDefinitions);
+ protected set
+ {
+ this.NpcDefinitions.Clear();
+ foreach (var item in value)
+ {
+ this.NpcDefinitions.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("gateDefenseUpgrades")]
+ public ICollection RawGateDefenseUpgrades { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection GateDefenseUpgrades
+ {
+ get => base.GateDefenseUpgrades ??= new CollectionAdapter(this.RawGateDefenseUpgrades);
+ protected set
+ {
+ this.GateDefenseUpgrades.Clear();
+ foreach (var item in value)
+ {
+ this.GateDefenseUpgrades.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("gateLifeUpgrades")]
+ public ICollection RawGateLifeUpgrades { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection GateLifeUpgrades
+ {
+ get => base.GateLifeUpgrades ??= new CollectionAdapter(this.RawGateLifeUpgrades);
+ protected set
+ {
+ this.GateLifeUpgrades.Clear();
+ foreach (var item in value)
+ {
+ this.GateLifeUpgrades.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("statueDefenseUpgrades")]
+ public ICollection RawStatueDefenseUpgrades { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection StatueDefenseUpgrades
+ {
+ get => base.StatueDefenseUpgrades ??= new CollectionAdapter(this.RawStatueDefenseUpgrades);
+ protected set
+ {
+ this.StatueDefenseUpgrades.Clear();
+ foreach (var item in value)
+ {
+ this.StatueDefenseUpgrades.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("statueLifeUpgrades")]
+ public ICollection RawStatueLifeUpgrades { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection StatueLifeUpgrades
+ {
+ get => base.StatueLifeUpgrades ??= new CollectionAdapter(this.RawStatueLifeUpgrades);
+ protected set
+ {
+ this.StatueLifeUpgrades.Clear();
+ foreach (var item in value)
+ {
+ this.StatueLifeUpgrades.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("statueRegenUpgrades")]
+ public ICollection RawStatueRegenUpgrades { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection StatueRegenUpgrades
+ {
+ get => base.StatueRegenUpgrades ??= new CollectionAdapter(this.RawStatueRegenUpgrades);
+ protected set
+ {
+ this.StatueRegenUpgrades.Clear();
+ foreach (var item in value)
+ {
+ this.StatueRegenUpgrades.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("attackMachineZones")]
+ public ICollection RawAttackMachineZones { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection AttackMachineZones
+ {
+ get => base.AttackMachineZones ??= new CollectionAdapter(this.RawAttackMachineZones);
+ protected set
+ {
+ this.AttackMachineZones.Clear();
+ foreach (var item in value)
+ {
+ this.AttackMachineZones.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("defenseMachineZones")]
+ public ICollection RawDefenseMachineZones { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection DefenseMachineZones
+ {
+ get => base.DefenseMachineZones ??= new CollectionAdapter(this.RawDefenseMachineZones);
+ protected set
+ {
+ this.DefenseMachineZones.Clear();
+ foreach (var item in value)
+ {
+ this.DefenseMachineZones.Add(item);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("castleSiegeMapDefinition")]
+ public GameMapDefinition RawCastleSiegeMapDefinition
+ {
+ get => base.CastleSiegeMapDefinition as GameMapDefinition;
+ set => base.CastleSiegeMapDefinition = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition CastleSiegeMapDefinition
+ {
+ get => base.CastleSiegeMapDefinition;
+ set => base.CastleSiegeMapDefinition = value;
+ }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("landOfTrialsMapDefinition")]
+ public GameMapDefinition RawLandOfTrialsMapDefinition
+ {
+ get => base.LandOfTrialsMapDefinition as GameMapDefinition;
+ set => base.LandOfTrialsMapDefinition = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition LandOfTrialsMapDefinition
+ {
+ get => base.LandOfTrialsMapDefinition;
+ set => base.LandOfTrialsMapDefinition = value;
+ }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("rewardItemDefinition")]
+ public ItemDefinition RawRewardItemDefinition
+ {
+ get => base.RewardItemDefinition as ItemDefinition;
+ set => base.RewardItemDefinition = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition RewardItemDefinition
+ {
+ get => base.RewardItemDefinition;
+ set => base.RewardItemDefinition = value;
+ }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("defenseRespawnArea")]
+ public CastleSiegeZoneDefinition RawDefenseRespawnArea
+ {
+ get => base.DefenseRespawnArea as CastleSiegeZoneDefinition;
+ set => base.DefenseRespawnArea = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition DefenseRespawnArea
+ {
+ get => base.DefenseRespawnArea;
+ set => base.DefenseRespawnArea = value;
+ }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("attackRespawnArea")]
+ public CastleSiegeZoneDefinition RawAttackRespawnArea
+ {
+ get => base.AttackRespawnArea as CastleSiegeZoneDefinition;
+ set => base.AttackRespawnArea = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition AttackRespawnArea
+ {
+ get => base.AttackRespawnArea;
+ set => base.AttackRespawnArea = value;
+ }
+
+ ///
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ var clone = new CastleSiegeConfiguration();
+ clone.AssignValuesOf(this, gameConfiguration);
+ return clone;
+ }
+
+ ///
+ public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ base.AssignValuesOf(other, gameConfiguration);
+ this.Id = other.GetId();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeConfiguration Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeData.Generated.cs b/src/Persistence/BasicModel/CastleSiegeData.Generated.cs
new file mode 100644
index 0000000..7341d24
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeData.Generated.cs
@@ -0,0 +1,67 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeData : MUnique.OpenMU.DataModel.Entities.CastleSiegeData, IIdentifiable, IConvertibleTo
+{
+
+
+
+ ///
+ /// Gets the raw collection of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("npcStates")]
+ public ICollection RawNpcStates { get; } = new List();
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override ICollection NpcStates
+ {
+ get => base.NpcStates ??= new CollectionAdapter(this.RawNpcStates);
+ protected set
+ {
+ this.NpcStates.Clear();
+ foreach (var item in value)
+ {
+ this.NpcStates.Add(item);
+ }
+ }
+ }
+
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeData Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeGuildRegistration.Generated.cs b/src/Persistence/BasicModel/CastleSiegeGuildRegistration.Generated.cs
new file mode 100644
index 0000000..4fb8b29
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeGuildRegistration.Generated.cs
@@ -0,0 +1,46 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeGuildRegistration : MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration, IIdentifiable, IConvertibleTo
+{
+
+
+
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeGuildRegistration Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeNpcDefinition.Generated.cs b/src/Persistence/BasicModel/CastleSiegeNpcDefinition.Generated.cs
new file mode 100644
index 0000000..47f16d1
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeNpcDefinition.Generated.cs
@@ -0,0 +1,81 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeNpcDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, IIdentifiable, IConvertibleTo
+{
+
+ ///
+ /// Gets or sets the identifier of this instance.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets the raw object of .
+ ///
+ [System.Text.Json.Serialization.JsonPropertyName("monsterDefinition")]
+ public MonsterDefinition RawMonsterDefinition
+ {
+ get => base.MonsterDefinition as MonsterDefinition;
+ set => base.MonsterDefinition = value;
+ }
+
+ ///
+ [System.Text.Json.Serialization.JsonIgnore]
+ public override MUnique.OpenMU.DataModel.Configuration.MonsterDefinition MonsterDefinition
+ {
+ get => base.MonsterDefinition;
+ set => base.MonsterDefinition = value;
+ }
+
+ ///
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ var clone = new CastleSiegeNpcDefinition();
+ clone.AssignValuesOf(this, gameConfiguration);
+ return clone;
+ }
+
+ ///
+ public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ base.AssignValuesOf(other, gameConfiguration);
+ this.Id = other.GetId();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeNpcDefinition Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs b/src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs
new file mode 100644
index 0000000..1367d5d
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs
@@ -0,0 +1,46 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeNpcState : MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, IIdentifiable, IConvertibleTo
+{
+
+
+
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeNpcState Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeStateScheduleEntry.Generated.cs b/src/Persistence/BasicModel/CastleSiegeStateScheduleEntry.Generated.cs
new file mode 100644
index 0000000..adc9f9d
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeStateScheduleEntry.Generated.cs
@@ -0,0 +1,63 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeStateScheduleEntry : MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, IIdentifiable, IConvertibleTo
+{
+
+ ///
+ /// Gets or sets the identifier of this instance.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ var clone = new CastleSiegeStateScheduleEntry();
+ clone.AssignValuesOf(this, gameConfiguration);
+ return clone;
+ }
+
+ ///
+ public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ base.AssignValuesOf(other, gameConfiguration);
+ this.Id = other.GetId();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeStateScheduleEntry Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeUpgradeDefinition.Generated.cs b/src/Persistence/BasicModel/CastleSiegeUpgradeDefinition.Generated.cs
new file mode 100644
index 0000000..744589b
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeUpgradeDefinition.Generated.cs
@@ -0,0 +1,63 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeUpgradeDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, IIdentifiable, IConvertibleTo
+{
+
+ ///
+ /// Gets or sets the identifier of this instance.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ var clone = new CastleSiegeUpgradeDefinition();
+ clone.AssignValuesOf(this, gameConfiguration);
+ return clone;
+ }
+
+ ///
+ public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ base.AssignValuesOf(other, gameConfiguration);
+ this.Id = other.GetId();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeUpgradeDefinition Convert() => this;
+}
diff --git a/src/Persistence/BasicModel/CastleSiegeZoneDefinition.Generated.cs b/src/Persistence/BasicModel/CastleSiegeZoneDefinition.Generated.cs
new file mode 100644
index 0000000..35ef3c8
--- /dev/null
+++ b/src/Persistence/BasicModel/CastleSiegeZoneDefinition.Generated.cs
@@ -0,0 +1,63 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+//------------------------------------------------------------------------------
+//
+// This source code was auto-generated by a roslyn code generator.
+//
+//------------------------------------------------------------------------------
+
+// ReSharper disable All
+
+namespace MUnique.OpenMU.Persistence.BasicModel;
+
+using MUnique.OpenMU.Persistence.Json;
+
+///
+/// A plain implementation of .
+///
+public partial class CastleSiegeZoneDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, IIdentifiable, IConvertibleTo
+{
+
+ ///
+ /// Gets or sets the identifier of this instance.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ var clone = new CastleSiegeZoneDefinition();
+ clone.AssignValuesOf(this, gameConfiguration);
+ return clone;
+ }
+
+ ///
+ public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
+ {
+ base.AssignValuesOf(other, gameConfiguration);
+ this.Id = other.GetId();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ var baseObject = obj as IIdentifiable;
+ if (baseObject != null)
+ {
+ return baseObject.Id == this.Id;
+ }
+
+ return base.Equals(obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ ///
+ public CastleSiegeZoneDefinition Convert() => this;
+}
diff --git a/src/Persistence/EntityFramework/EntityDataContext.cs b/src/Persistence/EntityFramework/EntityDataContext.cs
index d06f971..1950668 100644
--- a/src/Persistence/EntityFramework/EntityDataContext.cs
+++ b/src/Persistence/EntityFramework/EntityDataContext.cs
@@ -21,6 +21,16 @@ public class EntityDataContext : ExtendedTypeContext
///
internal GameConfiguration? CurrentGameConfiguration { get; set; }
+ ///
+ /// Gets the persistent Castle Siege state.
+ ///
+ internal DbSet CastleSiegeData => this.Set();
+
+ ///
+ /// Gets the Castle Siege guild registrations.
+ ///
+ internal DbSet CastleSiegeGuildRegistrations => this.Set();
+
///
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
@@ -58,6 +68,11 @@ public class EntityDataContext : ExtendedTypeContext
modelBuilder.Entity().Apply();
modelBuilder.Entity().Apply();
modelBuilder.Entity().Apply();
+ modelBuilder.Entity().Apply();
+ modelBuilder.Entity().Apply();
+ modelBuilder.Entity().Apply();
+ modelBuilder.Entity().Apply();
+ modelBuilder.Entity().Apply();
modelBuilder.Entity().Apply();
modelBuilder.Entity().Apply();
modelBuilder.Entity().Apply();
@@ -98,4 +113,4 @@ public class EntityDataContext : ExtendedTypeContext
GuildContext.ConfigureModel(modelBuilder);
FriendContext.ConfigureModel(modelBuilder);
}
-}
\ No newline at end of file
+}
diff --git a/src/Persistence/EntityFramework/EntityFrameworkContextBase.cs b/src/Persistence/EntityFramework/EntityFrameworkContextBase.cs
index 07c6ef8..ff16a74 100644
--- a/src/Persistence/EntityFramework/EntityFrameworkContextBase.cs
+++ b/src/Persistence/EntityFramework/EntityFrameworkContextBase.cs
@@ -68,6 +68,55 @@ internal class EntityFrameworkContextBase : IContext
///
public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ // A player's entities can be mutated by game logic on a flow that is not serialized against
+ // this save (for example item destruction on an attacker's thread during combat). Such a
+ // concurrent mutation makes change detection throw while it enumerates a tracked collection.
+ // The mutation is a single, quick operation, so a bounded retry lands on a stable moment
+ // instead of failing the whole save - which would otherwise leave the session unpersisted and
+ // roll the player back on relog.
+ const int maxAttempts = 3;
+ var attempt = 0;
+ while (true)
+ {
+ attempt++;
+ try
+ {
+ return await this.SaveChangesCoreAsync(cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex) when (attempt < maxAttempts && IsTransientConcurrencyConflict(ex))
+ {
+ this._logger.LogWarning(ex, "Transient concurrency conflict while saving (attempt {Attempt}/{MaxAttempts}); retrying.", attempt, maxAttempts);
+ await Task.Delay(attempt * 10, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ /// Determines whether the exception is a transient conflict caused by a concurrent entity mutation
+ /// racing this save, and is therefore worth retrying.
+ ///
+ /// The exception thrown by the save.
+ /// true if the save should be retried.
+ private static bool IsTransientConcurrencyConflict(Exception exception)
+ {
+ // A concurrent entity mutation racing this save corrupts the change tracker mid-enumeration.
+ // Depending on exactly where change detection was, it surfaces as one of several types - a
+ // modified collection (InvalidOperationException), a transiently-null internal key
+ // (ArgumentNullException/NullReferenceException), or an out-of-range index. All are transient:
+ // the racing mutation is a single quick operation, so a bounded retry lands on a stable moment.
+ // A genuinely persistent error of the same type is not masked - it rethrows once the retries
+ // are exhausted. The deterministic serialization (per-player persistence lock) is the primary
+ // guard; this retry only needs to absorb the rare, bursty sources that lock isn't held for.
+ return exception is DbUpdateConcurrencyException
+ or InvalidOperationException
+ or ArgumentNullException
+ or NullReferenceException
+ or IndexOutOfRangeException
+ or KeyNotFoundException;
+ }
+
+ private async ValueTask SaveChangesCoreAsync(CancellationToken cancellationToken)
{
using var l = await this._lock.LockAsync();
@@ -252,6 +301,14 @@ internal class EntityFrameworkContextBase : IContext
GC.SuppressFinalize(this);
}
+ ///
+ /// Determines whether changes of an entity type are published as configuration changes.
+ ///
+ /// The entity type.
+ /// when the entity belongs to the configuration schema.
+ internal static bool PublishesConfigurationChanges(IReadOnlyEntityType entityType)
+ => entityType.GetSchema() == SchemaNames.Configuration;
+
///
/// Releases unmanaged and - optionally - managed resources.
///
@@ -343,7 +400,9 @@ internal class EntityFrameworkContextBase : IContext
}
var changedEntries = this.Context.ChangeTracker.Entries()
- .Where(entity => entity.State != EntityState.Unchanged).ToList();
+ .Where(entity => entity.State != EntityState.Unchanged
+ && PublishesConfigurationChanges(entity.Metadata))
+ .ToList();
foreach (var entry in changedEntries)
{
var (parent, parentCollectionNavigation) = this.GetParentInformation(entry);
@@ -413,4 +472,4 @@ internal class EntityFrameworkContextBase : IContext
return (parent ?? parentId, parentCollectionNavigation);
}
-}
\ No newline at end of file
+}
diff --git a/src/Persistence/EntityFramework/Extensions/ModelBuilder/CastleSiegeExtensions.cs b/src/Persistence/EntityFramework/Extensions/ModelBuilder/CastleSiegeExtensions.cs
new file mode 100644
index 0000000..d01cf7d
--- /dev/null
+++ b/src/Persistence/EntityFramework/Extensions/ModelBuilder/CastleSiegeExtensions.cs
@@ -0,0 +1,85 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.EntityFramework.Extensions.ModelBuilder;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using MUnique.OpenMU.Persistence.EntityFramework.Model;
+
+///
+/// Extensions for Castle Siege-related s.
+///
+internal static class CastleSiegeExtensions
+{
+ ///
+ /// Applies the settings for the entity.
+ ///
+ /// The builder.
+ public static void Apply(this EntityTypeBuilder builder)
+ {
+ builder.Property(configuration => configuration.CrownHoldTimeSeconds).HasDefaultValue(30);
+ builder.Property(configuration => configuration.RegisterMinLevel).HasDefaultValue(200);
+ builder.Property(configuration => configuration.RegisterMinMembers).HasDefaultValue(20);
+ builder.Property(configuration => configuration.MaxAttackingGuilds).HasDefaultValue(3);
+
+ builder.HasOne(configuration => configuration.RawCastleSiegeMapDefinition)
+ .WithMany()
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne(configuration => configuration.RawLandOfTrialsMapDefinition)
+ .WithMany()
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne(configuration => configuration.RawRewardItemDefinition)
+ .WithMany()
+ .OnDelete(DeleteBehavior.Restrict);
+ }
+
+ ///
+ /// Applies the settings for the entity.
+ ///
+ /// The builder.
+ public static void Apply(this EntityTypeBuilder builder)
+ {
+ builder.HasOne(definition => definition.RawMonsterDefinition)
+ .WithMany()
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Restrict);
+ builder.HasIndex(definition => new { definition.MonsterDefinitionId, definition.InstanceId });
+ }
+
+ ///
+ /// Applies the settings for the entity.
+ ///
+ /// The builder.
+ public static void Apply(this EntityTypeBuilder builder)
+ {
+ builder.HasOne()
+ .WithMany()
+ .HasForeignKey(data => data.OwnerGuildId)
+ .OnDelete(DeleteBehavior.SetNull);
+ }
+
+ ///
+ /// Applies the settings for the entity.
+ ///
+ /// The builder.
+ public static void Apply(this EntityTypeBuilder builder)
+ {
+ builder.HasIndex(state => new { state.MonsterNumber, state.InstanceId }).IsUnique();
+ }
+
+ ///
+ /// Applies the settings for the entity.
+ ///
+ /// The builder.
+ public static void Apply(this EntityTypeBuilder builder)
+ {
+ builder.Property(registration => registration.GuildName).HasMaxLength(8).IsRequired();
+ builder.HasIndex(registration => registration.GuildId).IsUnique();
+ builder.HasOne()
+ .WithMany()
+ .HasForeignKey(registration => registration.GuildId)
+ .OnDelete(DeleteBehavior.Cascade);
+ }
+}
diff --git a/src/Persistence/EntityFramework/Migrations/20260710205741_AddIsQuestItemFlag.cs b/src/Persistence/EntityFramework/Migrations/20260710205741_AddIsQuestItemFlag.cs
index dcdc8bd..b6ea120 100644
--- a/src/Persistence/EntityFramework/Migrations/20260710205741_AddIsQuestItemFlag.cs
+++ b/src/Persistence/EntityFramework/Migrations/20260710205741_AddIsQuestItemFlag.cs
@@ -1,9 +1,13 @@
-using Microsoft.EntityFrameworkCore.Migrations;
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
#nullable disable
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
{
+ using Microsoft.EntityFrameworkCore.Migrations;
+
///
public partial class AddIsQuestItemFlag : Migration
{
diff --git a/src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.cs b/src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.cs
index acac7e4..9e6441e 100644
--- a/src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.cs
+++ b/src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.cs
@@ -1,10 +1,14 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
#nullable disable
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
{
+ using System;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
///
public partial class AddBuff : Migration
{
@@ -20,7 +24,7 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
MagicEffectDefinitionId = table.Column(type: "uuid", nullable: true),
MonsterDefinitionId = table.Column(type: "uuid", nullable: true),
MinimumLevel = table.Column(type: "integer", nullable: true),
- MaximumLevel = table.Column(type: "integer", nullable: true)
+ MaximumLevel = table.Column(type: "integer", nullable: true),
},
constraints: table =>
{
diff --git a/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs b/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs
new file mode 100644
index 0000000..ffe119e
--- /dev/null
+++ b/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs
@@ -0,0 +1,5725 @@
+//
+using System;
+using MUnique.OpenMU.Persistence.EntityFramework;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
+{
+ [DbContext(typeof(EntityDataContext))]
+ [Migration("20260730194321_AddCastleSiege")]
+ partial class AddCastleSiege
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.2")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChatBanUntil")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EMail")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsBot")
+ .HasColumnType("boolean");
+
+ b.Property("IsTemplate")
+ .HasColumnType("boolean");
+
+ b.Property("IsVaultExtended")
+ .HasColumnType("boolean");
+
+ b.Property("LanguageIsoCode")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(3)
+ .HasColumnType("character varying(3)")
+ .HasDefaultValue("en");
+
+ b.Property("LoginName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RegistrationDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecurityCode")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.Property("TimeZone")
+ .HasColumnType("smallint");
+
+ b.Property("VaultId")
+ .HasColumnType("uuid");
+
+ b.Property("VaultPassword")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LoginName")
+ .IsUnique();
+
+ b.HasIndex("VaultId")
+ .IsUnique();
+
+ b.ToTable("Account", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AccountCharacterClass", b =>
+ {
+ b.Property("AccountId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.HasKey("AccountId", "CharacterClassId");
+
+ b.HasIndex("CharacterClassId");
+
+ b.ToTable("AccountCharacterClass", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("FullAncientSetEquipped")
+ .HasColumnType("boolean");
+
+ b.Property("Pose")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.ToTable("AppearanceData", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AreaSkillSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("DelayBetweenHits")
+ .HasColumnType("interval");
+
+ b.Property("DelayPerOneDistance")
+ .HasColumnType("interval");
+
+ b.Property("EffectRange")
+ .HasColumnType("integer");
+
+ b.Property("FrustumDistance")
+ .HasColumnType("real");
+
+ b.Property("FrustumEndWidth")
+ .HasColumnType("real");
+
+ b.Property("FrustumStartWidth")
+ .HasColumnType("real");
+
+ b.Property("HitChancePerDistanceMultiplier")
+ .HasColumnType("real");
+
+ b.Property("MaximumNumberOfHitsPerAttack")
+ .HasColumnType("integer");
+
+ b.Property("MaximumNumberOfHitsPerTarget")
+ .HasColumnType("integer");
+
+ b.Property("MinimumNumberOfHitsPerAttack")
+ .HasColumnType("integer");
+
+ b.Property("MinimumNumberOfHitsPerTarget")
+ .HasColumnType("integer");
+
+ b.Property("ProjectileCount")
+ .HasColumnType("integer");
+
+ b.Property("TargetAreaDiameter")
+ .HasColumnType("real");
+
+ b.Property("UseDeferredHits")
+ .HasColumnType("boolean");
+
+ b.Property("UseFrustumFilter")
+ .HasColumnType("boolean");
+
+ b.Property("UseTargetAreaFilter")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.ToTable("AreaSkillSettings", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Designation")
+ .HasColumnType("text");
+
+ b.Property("GameConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("MaximumValue")
+ .HasColumnType("real");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GameConfigurationId");
+
+ b.ToTable("AttributeDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRelationship", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AggregateType")
+ .HasColumnType("integer");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("GameConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("InputAttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("InputOperand")
+ .HasColumnType("real");
+
+ b.Property("InputOperator")
+ .HasColumnType("integer");
+
+ b.Property("OperandAttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("PowerUpDefinitionValueId")
+ .HasColumnType("uuid");
+
+ b.Property("SkillId")
+ .HasColumnType("uuid");
+
+ b.Property("TargetAttributeId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("GameConfigurationId");
+
+ b.HasIndex("InputAttributeId");
+
+ b.HasIndex("OperandAttributeId");
+
+ b.HasIndex("PowerUpDefinitionValueId");
+
+ b.HasIndex("SkillId");
+
+ b.HasIndex("TargetAttributeId");
+
+ b.ToTable("AttributeRelationship", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRequirement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("GameMapDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("ItemDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("MinimumValue")
+ .HasColumnType("integer");
+
+ b.Property("SkillId")
+ .HasColumnType("uuid");
+
+ b.Property("SkillId1")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AttributeId");
+
+ b.HasIndex("GameMapDefinitionId");
+
+ b.HasIndex("ItemDefinitionId");
+
+ b.HasIndex("SkillId");
+
+ b.HasIndex("SkillId1");
+
+ b.ToTable("AttributeRequirement", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("GroundId")
+ .HasColumnType("uuid");
+
+ b.Property("LeftGoalId")
+ .HasColumnType("uuid");
+
+ b.Property("LeftTeamSpawnPointX")
+ .HasColumnType("smallint");
+
+ b.Property("LeftTeamSpawnPointY")
+ .HasColumnType("smallint");
+
+ b.Property("RightGoalId")
+ .HasColumnType("uuid");
+
+ b.Property("RightTeamSpawnPointX")
+ .HasColumnType("smallint");
+
+ b.Property("RightTeamSpawnPointY")
+ .HasColumnType("smallint");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GroundId")
+ .IsUnique();
+
+ b.HasIndex("LeftGoalId")
+ .IsUnique();
+
+ b.HasIndex("RightGoalId")
+ .IsUnique();
+
+ b.ToTable("BattleZoneDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("MagicEffectDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("MaximumLevel")
+ .HasColumnType("integer");
+
+ b.Property("MinimumLevel")
+ .HasColumnType("integer");
+
+ b.Property("MonsterDefinitionId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MagicEffectDefinitionId")
+ .IsUnique();
+
+ b.HasIndex("MonsterDefinitionId");
+
+ b.ToTable("Buff", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AttackRespawnAreaId")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeMapDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("CrownHoldTimeSeconds")
+ .HasColumnType("integer");
+
+ b.Property("DefenseRespawnAreaId")
+ .HasColumnType("uuid");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("GateBuyPrice")
+ .HasColumnType("integer");
+
+ b.Property("GuildScoreCastleSiege")
+ .HasColumnType("integer");
+
+ b.Property("GuildScoreCastleSiegeMembers")
+ .HasColumnType("integer");
+
+ b.Property("LandOfTrialsMapDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("MaxAttackingGuilds")
+ .HasColumnType("integer");
+
+ b.Property("ParticipantRewardMinSeconds")
+ .HasColumnType("integer");
+
+ b.Property("RegisterMinLevel")
+ .HasColumnType("integer");
+
+ b.Property("RegisterMinMembers")
+ .HasColumnType("integer");
+
+ b.Property("RewardItemDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("StatueBuyPrice")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AttackRespawnAreaId")
+ .IsUnique();
+
+ b.HasIndex("CastleSiegeMapDefinitionId");
+
+ b.HasIndex("DefenseRespawnAreaId")
+ .IsUnique();
+
+ b.HasIndex("LandOfTrialsMapDefinitionId");
+
+ b.HasIndex("RewardItemDefinitionId");
+
+ b.ToTable("CastleSiegeConfiguration", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("IsHuntZoneEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("IsOccupied")
+ .HasColumnType("boolean");
+
+ b.Property("OwnerGuildId")
+ .HasColumnType("uuid");
+
+ b.Property("TaxChaos")
+ .HasColumnType("smallint");
+
+ b.Property("TaxHunt")
+ .HasColumnType("integer");
+
+ b.Property("TaxStore")
+ .HasColumnType("smallint");
+
+ b.Property("TributeMoney")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.ToTable("CastleSiegeData", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("DefaultSide")
+ .HasColumnType("smallint");
+
+ b.Property("Direction")
+ .HasColumnType("integer");
+
+ b.Property("InstanceId")
+ .HasColumnType("smallint");
+
+ b.Property("IsPersistedToDatabase")
+ .HasColumnType("boolean");
+
+ b.Property("MonsterDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("SpawnX")
+ .HasColumnType("smallint");
+
+ b.Property("SpawnY")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CastleSiegeConfigurationId");
+
+ b.HasIndex("MonsterDefinitionId");
+
+ b.ToTable("CastleSiegeNpcDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeDataId")
+ .HasColumnType("uuid");
+
+ b.Property("CurrentHp")
+ .HasColumnType("integer");
+
+ b.Property("DefenseLevel")
+ .HasColumnType("smallint");
+
+ b.Property("InstanceId")
+ .HasColumnType("smallint");
+
+ b.Property("LifeLevel")
+ .HasColumnType("smallint");
+
+ b.Property("MonsterNumber")
+ .HasColumnType("smallint");
+
+ b.Property("RegenLevel")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CastleSiegeDataId");
+
+ b.ToTable("CastleSiegeNpcState", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("DayOfWeek")
+ .HasColumnType("integer");
+
+ b.Property("Hour")
+ .HasColumnType("smallint");
+
+ b.Property("Minute")
+ .HasColumnType("smallint");
+
+ b.Property("State")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CastleSiegeConfigurationId");
+
+ b.ToTable("CastleSiegeStateScheduleEntry", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId1")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId2")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId3")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId4")
+ .HasColumnType("uuid");
+
+ b.Property("Level")
+ .HasColumnType("smallint");
+
+ b.Property("RequiredJewelOfGuardianCount")
+ .HasColumnType("integer");
+
+ b.Property("RequiredZen")
+ .HasColumnType("integer");
+
+ b.Property("Value")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CastleSiegeConfigurationId");
+
+ b.HasIndex("CastleSiegeConfigurationId1");
+
+ b.HasIndex("CastleSiegeConfigurationId2");
+
+ b.HasIndex("CastleSiegeConfigurationId3");
+
+ b.HasIndex("CastleSiegeConfigurationId4");
+
+ b.ToTable("CastleSiegeUpgradeDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("CastleSiegeConfigurationId1")
+ .HasColumnType("uuid");
+
+ b.Property("X1")
+ .HasColumnType("smallint");
+
+ b.Property("X2")
+ .HasColumnType("smallint");
+
+ b.Property("Y1")
+ .HasColumnType("smallint");
+
+ b.Property("Y2")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CastleSiegeConfigurationId");
+
+ b.HasIndex("CastleSiegeConfigurationId1");
+
+ b.ToTable("CastleSiegeZoneDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterSlot")
+ .HasColumnType("smallint");
+
+ b.Property("CharacterStatus")
+ .HasColumnType("integer");
+
+ b.Property("CreateDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CurrentMapId")
+ .HasColumnType("uuid");
+
+ b.Property("Experience")
+ .HasColumnType("bigint");
+
+ b.Property("InventoryExtensions")
+ .HasColumnType("integer");
+
+ b.Property("InventoryId")
+ .HasColumnType("uuid");
+
+ b.Property("IsStoreOpened")
+ .HasColumnType("boolean");
+
+ b.Property("KeyConfiguration")
+ .HasColumnType("bytea");
+
+ b.Property("LevelUpPoints")
+ .HasColumnType("integer");
+
+ b.Property("MasterExperience")
+ .HasColumnType("bigint");
+
+ b.Property("MasterLevelUpPoints")
+ .HasColumnType("integer");
+
+ b.Property("MuHelperConfiguration")
+ .HasColumnType("bytea");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("PlayerKillCount")
+ .HasColumnType("integer");
+
+ b.Property("Pose")
+ .HasColumnType("smallint");
+
+ b.Property("PositionX")
+ .HasColumnType("smallint");
+
+ b.Property("PositionY")
+ .HasColumnType("smallint");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.Property("StateRemainingSeconds")
+ .HasColumnType("integer");
+
+ b.Property("StoreName")
+ .HasColumnType("text");
+
+ b.Property("UsedFruitPoints")
+ .HasColumnType("integer");
+
+ b.Property("UsedNegFruitPoints")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("CurrentMapId");
+
+ b.HasIndex("InventoryId")
+ .IsUnique();
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Character", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property