diff --git a/src/DataModel/Configuration/CastleSiegeConfiguration.cs b/src/DataModel/Configuration/CastleSiegeConfiguration.cs
new file mode 100644
index 000000000..595d67355
--- /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 000000000..bae4d83a9
--- /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 000000000..b3d3fc000
--- /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 short SpawnX { get; set; }
+
+ ///
+ /// Gets or sets the Y coordinate of the NPC's spawn position.
+ ///
+ public short 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 000000000..82b5a2426
--- /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 000000000..3065f40c8
--- /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 000000000..dd372c1fa
--- /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 000000000..e5a6bed3f
--- /dev/null
+++ b/src/DataModel/Configuration/CastleSiegeUpgradeType.cs
@@ -0,0 +1,26 @@
+//
+// 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
+{
+ ///
+ /// 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 000000000..602042bcc
--- /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 564e1813f..9f6941a16 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/Configuration/MonsterDefinition.cs b/src/DataModel/Configuration/MonsterDefinition.cs
index fca37dd56..54ade48fd 100644
--- a/src/DataModel/Configuration/MonsterDefinition.cs
+++ b/src/DataModel/Configuration/MonsterDefinition.cs
@@ -165,6 +165,16 @@ public enum NpcWindow
/// The dialog for the legacy quest system.
///
LegacyQuest,
+
+ ///
+ /// The castle siege gate NPC interaction window.
+ ///
+ CastleSiegeGateNpc,
+
+ ///
+ /// The castle siege lever NPC interaction window.
+ ///
+ CastleSiegeLeverNpc,
}
///
diff --git a/src/DataModel/Entities/CastleSiegeData.cs b/src/DataModel/Entities/CastleSiegeData.cs
new file mode 100644
index 000000000..5067fcd98
--- /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 000000000..39119ae81
--- /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 000000000..509a0d8b7
--- /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})";
+ }
+}