From 3aa9815b106644b04b71383f35eea36919f08fcb Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 4 Aug 2026 03:28:10 +0300 Subject: [PATCH 1/4] feat(castle-siege): adopt the upstream Castle Siege data model and persistence Brings in the database layer of upstream OpenMU PRs #754 and #860 without touching AdaMu's working Castle Siege gameplay. This is purely additive: the existing 5-phase implementation still runs exactly as before. What is included: - DataModel: CastleSiegeState (the original Season 6 values 0-9, which are exactly what the game client's CASTLESIEGE_STATE enum expects), CastleSiegeJoinSide, and the zone/NPC/upgrade definition types. - Entities: CastleSiegeData, CastleSiegeGuildRegistration, CastleSiegeNpcState. These identify a guild by its persistent Guid rather than by name. - Generated persistence: 8 BasicModel + 8 EntityFramework model classes, CastleSiegeExtensions, and the regenerated ExtendedTypeContext, MapsterConfigurator and GameConfiguration partials. - Migrations: 20260730194321_AddCastleSiege and 20260801162427_ConfigureCastleSiegePersistence, plus the model snapshot. - EntityDataContext gains the two DbSets and the five model registrations. - EntityFrameworkContextBase only publishes configuration changes for entities in the configuration schema, so siege state writes are no longer broadcast as configuration changes. AdaMu-specific adaptations: - UpdateVersion.AddCastleSiegeData is 105, not upstream's 100. AdaMu already ships 95-104, and the applied-update bookkeeping is keyed on this value, so a collision would skip or re-run updates on live databases. - CastleSiegeInitializer does not seed a weekly StateSchedule. Upstream drives the cycle from a fixed Saturday schedule; AdaMu drives it manually from CastleSiegeEventPlugIn and the AdminPanel, so the schedule is left empty and nothing reads it. The seeded NPC definitions match AdaMu's existing hard-coded coordinates exactly (6 gates, the two crown switches and the crown), and additionally provide 4 guardian statues, 6 guardsmen and real gate/statue hit point tables that the current implementation does not have yet. Two pre-existing migrations were restyled by upstream (copyright header, using placement, trailing comma). No functional change. Verified: full server build succeeds with 0 errors. --- .../Configuration/CastleSiegeConfiguration.cs | 152 + .../Configuration/CastleSiegeJoinSide.cs | 36 + .../Configuration/CastleSiegeNpcDefinition.cs | 55 + .../Configuration/CastleSiegeState.cs | 61 + .../CastleSiegeStateScheduleEntry.cs | 40 + .../CastleSiegeUpgradeDefinition.cs | 40 + .../Configuration/CastleSiegeUpgradeType.cs | 31 + .../CastleSiegeZoneDefinition.cs | 40 + .../Configuration/GameConfiguration.cs | 6 + src/DataModel/Entities/CastleSiegeData.cs | 67 + .../Entities/CastleSiegeGuildRegistration.cs | 44 + src/DataModel/Entities/CastleSiegeNpcState.cs | 52 + .../CastleSiegeConfiguration.Generated.cs | 342 + .../BasicModel/CastleSiegeData.Generated.cs | 67 + .../CastleSiegeGuildRegistration.Generated.cs | 46 + .../CastleSiegeNpcDefinition.Generated.cs | 81 + .../CastleSiegeNpcState.Generated.cs | 46 + ...CastleSiegeStateScheduleEntry.Generated.cs | 63 + .../CastleSiegeUpgradeDefinition.Generated.cs | 63 + .../CastleSiegeZoneDefinition.Generated.cs | 63 + .../EntityFramework/EntityDataContext.cs | 17 +- .../EntityFrameworkContextBase.cs | 63 +- .../ModelBuilder/CastleSiegeExtensions.cs | 85 + .../20260710205741_AddIsQuestItemFlag.cs | 6 +- .../Migrations/20260712014203_AddBuff.cs | 10 +- .../20260730194321_AddCastleSiege.Designer.cs | 5725 ++++++++++++++++ .../20260730194321_AddCastleSiege.cs | 453 ++ ...onfigureCastleSiegePersistence.Designer.cs | 5788 +++++++++++++++++ ...1162427_ConfigureCastleSiegePersistence.cs | 343 + .../EntityDataContextModelSnapshot.cs | 495 ++ .../CastleSiegeConfiguration.Generated.cs | 276 + .../Model/CastleSiegeData.Generated.cs | 56 + .../CastleSiegeGuildRegistration.Generated.cs | 47 + .../CastleSiegeNpcDefinition.Generated.cs | 91 + .../Model/CastleSiegeNpcState.Generated.cs | 47 + ...CastleSiegeStateScheduleEntry.Generated.cs | 65 + .../CastleSiegeUpgradeDefinition.Generated.cs | 65 + .../CastleSiegeZoneDefinition.Generated.cs | 65 + .../Updates/AddCastleSiegeDataUpdatePlugIn.cs | 59 + .../Initialization/Updates/UpdateVersion.cs | 10 + .../Events/CastleSiegeInitializer.cs | 228 + .../GameConfigurationInitializer.cs | 1 + 42 files changed, 15383 insertions(+), 7 deletions(-) create mode 100644 src/DataModel/Configuration/CastleSiegeConfiguration.cs create mode 100644 src/DataModel/Configuration/CastleSiegeJoinSide.cs create mode 100644 src/DataModel/Configuration/CastleSiegeNpcDefinition.cs create mode 100644 src/DataModel/Configuration/CastleSiegeState.cs create mode 100644 src/DataModel/Configuration/CastleSiegeStateScheduleEntry.cs create mode 100644 src/DataModel/Configuration/CastleSiegeUpgradeDefinition.cs create mode 100644 src/DataModel/Configuration/CastleSiegeUpgradeType.cs create mode 100644 src/DataModel/Configuration/CastleSiegeZoneDefinition.cs create mode 100644 src/DataModel/Entities/CastleSiegeData.cs create mode 100644 src/DataModel/Entities/CastleSiegeGuildRegistration.cs create mode 100644 src/DataModel/Entities/CastleSiegeNpcState.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeConfiguration.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeData.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeGuildRegistration.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeNpcDefinition.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeNpcState.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeStateScheduleEntry.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeUpgradeDefinition.Generated.cs create mode 100644 src/Persistence/BasicModel/CastleSiegeZoneDefinition.Generated.cs create mode 100644 src/Persistence/EntityFramework/Extensions/ModelBuilder/CastleSiegeExtensions.cs create mode 100644 src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.Designer.cs create mode 100644 src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.cs create mode 100644 src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs create mode 100644 src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeConfiguration.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeData.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeGuildRegistration.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeNpcDefinition.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeNpcState.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeStateScheduleEntry.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeUpgradeDefinition.Generated.cs create mode 100644 src/Persistence/EntityFramework/Model/CastleSiegeZoneDefinition.Generated.cs create mode 100644 src/Persistence/Initialization/Updates/AddCastleSiegeDataUpdatePlugIn.cs create mode 100644 src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs 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("CanGetCreated") + .HasColumnType("boolean"); + + b.Property("ComboDefinitionId") + .HasColumnType("uuid"); + + b.Property("CreationAllowedFlag") + .HasColumnType("smallint"); + + b.Property("FruitCalculation") + .HasColumnType("integer"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("HomeMapId") + .HasColumnType("uuid"); + + b.Property("IsMasterClass") + .HasColumnType("boolean"); + + b.Property("LevelRequirementByCreation") + .HasColumnType("smallint"); + + b.Property("LevelWarpRequirementReductionPercent") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NextGenerationClassId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ComboDefinitionId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("HomeMapId"); + + b.HasIndex("NextGenerationClassId"); + + b.ToTable("CharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterDropItemGroup", b => + { + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("CharacterId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("CharacterDropItemGroup", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveQuestId") + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("ClientActionPerformed") + .HasColumnType("boolean"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("LastFinishedQuestId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ActiveQuestId"); + + b.HasIndex("CharacterId"); + + b.HasIndex("LastFinishedQuestId"); + + b.ToTable("CharacterQuestState", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientCleanUpInterval") + .HasColumnType("interval"); + + b.Property("ClientTimeout") + .HasColumnType("interval"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("MaximumConnections") + .HasColumnType("integer"); + + b.Property("RoomCleanUpInterval") + .HasColumnType("interval"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("ChatServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChatServerDefinitionId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("NetworkPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChatServerDefinitionId"); + + b.HasIndex("ClientId"); + + b.ToTable("ChatServerEndpoint", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CombinationBonusRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemOptionCombinationBonusId") + .HasColumnType("uuid"); + + b.Property("MinimumCount") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemOptionCombinationBonusId"); + + b.HasIndex("OptionTypeId"); + + b.ToTable("CombinationBonusRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ConfigurationUpdate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdateState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrentInstalledVersion") + .HasColumnType("integer"); + + b.Property("InitializationKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ConfigurationUpdateState", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConnectServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckMaxConnectionsPerAddress") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientListenerPort") + .HasColumnType("integer"); + + b.Property("CurrentPatchVersion") + .HasColumnType("bytea"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisconnectOnUnknownPacket") + .HasColumnType("boolean"); + + b.Property("ListenerBacklog") + .HasColumnType("integer"); + + b.Property("MaxConnections") + .HasColumnType("integer"); + + b.Property("MaxConnectionsPerAddress") + .HasColumnType("integer"); + + b.Property("MaxFtpRequests") + .HasColumnType("integer"); + + b.Property("MaxIpRequests") + .HasColumnType("integer"); + + b.Property("MaxServerListRequests") + .HasColumnType("integer"); + + b.Property("MaximumReceiveSize") + .HasColumnType("smallint"); + + b.Property("PatchAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.Property("Timeout") + .HasColumnType("interval"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ConnectServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConstValueAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("CharacterClassId"); + + b.HasIndex("DefinitionId"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ConstValueAttribute", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Chance") + .HasColumnType("double precision"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("ItemLevel") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("integer"); + + b.Property("MaximumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MinimumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MonsterId"); + + b.ToTable("DropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroupItemDefinition", b => + { + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("DropItemGroupId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("DropItemGroupItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DuelConfigurationId") + .HasColumnType("uuid"); + + b.Property("FirstPlayerGateId") + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("smallint"); + + b.Property("SecondPlayerGateId") + .HasColumnType("uuid"); + + b.Property("SpectatorsGateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DuelConfigurationId"); + + b.HasIndex("FirstPlayerGateId"); + + b.HasIndex("SecondPlayerGateId"); + + b.HasIndex("SpectatorsGateId"); + + b.ToTable("DuelArea", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EntranceFee") + .HasColumnType("integer"); + + b.Property("ExitId") + .HasColumnType("uuid"); + + b.Property("MaximumScore") + .HasColumnType("integer"); + + b.Property("MaximumSpectatorsPerDuelRoom") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ExitId"); + + b.ToTable("DuelConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.EnterGate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("LevelRequirement") + .HasColumnType("smallint"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("TargetGateId") + .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("GameMapDefinitionId"); + + b.HasIndex("TargetGateId"); + + b.ToTable("EnterGate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("IsSpawnGate") + .HasColumnType("boolean"); + + b.Property("MapId") + .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("MapId"); + + b.ToTable("ExitGate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Accepted") + .HasColumnType("boolean"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("FriendId") + .HasColumnType("uuid"); + + b.Property("RequestOpen") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasAlternateKey("CharacterId", "FriendId"); + + b.ToTable("Friend", "friend"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Episode") + .HasColumnType("smallint"); + + b.Property("Language") + .HasColumnType("integer"); + + b.Property("Season") + .HasColumnType("smallint"); + + b.Property("Serial") + .HasColumnType("bytea"); + + b.Property("Version") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.ToTable("GameClientDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSkillHitsPlayer") + .HasColumnType("boolean"); + + b.Property("CastleSiegeConfigurationId") + .HasColumnType("uuid"); + + b.Property("CharacterNameRegex") + .HasColumnType("text"); + + b.Property("ClampMoneyOnPickup") + .HasColumnType("boolean"); + + b.Property("DamagePerOneItemDurability") + .HasColumnType("double precision"); + + b.Property("DamagePerOnePetDurability") + .HasColumnType("double precision"); + + b.Property("DuelConfigurationId") + .HasColumnType("uuid"); + + b.Property("ExcellentItemDropLevelDelta") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((byte)25); + + b.Property("ExperienceFormula") + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("if(level == 0, 0, if(level < 256, 10 * (level + 8) * (level - 1) * (level - 1), (10 * (level + 8) * (level - 1) * (level - 1)) + (1000 * (level - 247) * (level - 256) * (level - 256))))"); + + b.Property("ExperienceRate") + .HasColumnType("real"); + + b.Property("HitsPerOneItemDurability") + .HasColumnType("double precision"); + + b.Property("InfoRange") + .HasColumnType("smallint"); + + b.Property("ItemDropDuration") + .ValueGeneratedOnAdd() + .HasColumnType("interval") + .HasDefaultValue(new TimeSpan(0, 0, 1, 0, 0)); + + b.Property("LetterSendPrice") + .HasColumnType("integer"); + + b.Property("MasterExperienceFormula") + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("(505 * level * level * level) + (35278500 * level) + (228045 * level * level)"); + + b.Property("MasterExperienceRate") + .HasColumnType("real"); + + b.Property("MaximumCharactersPerAccount") + .HasColumnType("smallint"); + + b.Property("MaximumInventoryMoney") + .HasColumnType("integer"); + + b.Property("MaximumItemOptionLevelDrop") + .HasColumnType("smallint"); + + b.Property("MaximumLetters") + .HasColumnType("integer"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MaximumMasterLevel") + .HasColumnType("smallint"); + + b.Property("MaximumPartySize") + .HasColumnType("smallint"); + + b.Property("MaximumPasswordLength") + .HasColumnType("integer"); + + b.Property("MaximumVaultMoney") + .HasColumnType("integer"); + + b.Property("MinimumMonsterLevelForMasterExperience") + .HasColumnType("smallint"); + + b.Property("PreventExperienceOverflow") + .HasColumnType("boolean"); + + b.Property("RecoveryInterval") + .HasColumnType("integer"); + + b.Property("ShouldDropMoney") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CastleSiegeConfigurationId") + .IsUnique(); + + b.HasIndex("DuelConfigurationId") + .IsUnique(); + + b.ToTable("GameConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BattleZoneId") + .HasColumnType("uuid"); + + b.Property("Discriminator") + .HasColumnType("integer"); + + b.Property("ExpMultiplier") + .HasColumnType("double precision"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SafezoneMapId") + .HasColumnType("uuid"); + + b.Property("TerrainData") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("BattleZoneId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("SafezoneMapId"); + + b.ToTable("GameMapDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinitionDropItemGroup", b => + { + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("GameMapDefinitionId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("GameMapDefinitionDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumPlayers") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("GameServerConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfigurationGameMapDefinition", b => + { + b.Property("GameServerConfigurationId") + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("GameServerConfigurationId", "GameMapDefinitionId"); + + b.HasIndex("GameMapDefinitionId"); + + b.ToTable("GameServerConfigurationGameMapDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExperienceRate") + .HasColumnType("real"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("PvpEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ServerConfigurationId") + .HasColumnType("uuid"); + + b.Property("ServerID") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("ServerConfigurationId"); + + b.ToTable("GameServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlternativePublishedPort") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("GameServerDefinitionId") + .HasColumnType("uuid"); + + b.Property("NetworkPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("GameServerDefinitionId"); + + b.ToTable("GameServerEndpoint", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllianceGuildId") + .HasColumnType("uuid"); + + b.Property("HostilityId") + .HasColumnType("uuid"); + + b.Property("Logo") + .HasColumnType("bytea"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Notice") + .HasColumnType("text"); + + b.Property("Score") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AllianceGuildId"); + + b.HasIndex("HostilityId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Guild", "guild"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GuildMember", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildMember", "guild"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemOptionDefinitionId") + .HasColumnType("uuid"); + + b.Property("LevelType") + .HasColumnType("integer"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.Property("Weight") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ItemOptionDefinitionId"); + + b.HasIndex("OptionTypeId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("IncreasableItemOption", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("Durability") + .HasColumnType("double precision"); + + b.Property("HasSkill") + .HasColumnType("boolean"); + + b.Property("ItemSlot") + .HasColumnType("smallint"); + + b.Property("ItemStorageId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("PetExperience") + .HasColumnType("integer"); + + b.Property("SocketCount") + .HasColumnType("integer"); + + b.Property("StorePrice") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DefinitionId"); + + b.HasIndex("ItemStorageId"); + + b.ToTable("Item", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppearanceDataId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSlot") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AppearanceDataId"); + + b.HasIndex("DefinitionId"); + + b.ToTable("ItemAppearance", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearanceItemOptionType", b => + { + b.Property("ItemAppearanceId") + .HasColumnType("uuid"); + + b.Property("ItemOptionTypeId") + .HasColumnType("uuid"); + + b.HasKey("ItemAppearanceId", "ItemOptionTypeId"); + + b.HasIndex("ItemOptionTypeId"); + + b.ToTable("ItemAppearanceItemOptionType", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemBasePowerUpDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AggregateType") + .HasColumnType("integer"); + + b.Property("BaseValue") + .HasColumnType("real"); + + b.Property("BonusPerLevelTableId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BonusPerLevelTableId"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("ItemBasePowerUpDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemCraftingHandlerClassName") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonsterDefinitionId"); + + b.HasIndex("SimpleCraftingSettingsId") + .IsUnique(); + + b.ToTable("ItemCrafting", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddPercentage") + .HasColumnType("smallint"); + + b.Property("FailResult") + .HasColumnType("integer"); + + b.Property("MaximumAmount") + .HasColumnType("smallint"); + + b.Property("MaximumItemLevel") + .HasColumnType("smallint"); + + b.Property("MinimumAmount") + .HasColumnType("smallint"); + + b.Property("MinimumItemLevel") + .HasColumnType("smallint"); + + b.Property("NpcPriceDivisor") + .HasColumnType("integer"); + + b.Property("Reference") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.Property("SuccessResult") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SimpleCraftingSettingsId"); + + b.ToTable("ItemCraftingRequiredItem", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemDefinition", b => + { + b.Property("ItemCraftingRequiredItemId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemCraftingRequiredItemId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("ItemCraftingRequiredItemItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemOptionType", b => + { + b.Property("ItemCraftingRequiredItemId") + .HasColumnType("uuid"); + + b.Property("ItemOptionTypeId") + .HasColumnType("uuid"); + + b.HasKey("ItemCraftingRequiredItemId", "ItemOptionTypeId"); + + b.HasIndex("ItemOptionTypeId"); + + b.ToTable("ItemCraftingRequiredItemItemOptionType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingResultItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddLevel") + .HasColumnType("smallint"); + + b.Property("Durability") + .HasColumnType("smallint"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("RandomMaximumLevel") + .HasColumnType("smallint"); + + b.Property("RandomMinimumLevel") + .HasColumnType("smallint"); + + b.Property("Reference") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("SimpleCraftingSettingsId"); + + b.ToTable("ItemCraftingResultItem", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConsumeEffectId") + .HasColumnType("uuid"); + + b.Property("DropLevel") + .HasColumnType("smallint"); + + b.Property("DropsFromMonsters") + .HasColumnType("boolean"); + + b.Property("Durability") + .HasColumnType("smallint"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("Height") + .HasColumnType("smallint"); + + b.Property("IsAmmunition") + .HasColumnType("boolean"); + + b.Property("IsBoundToCharacter") + .HasColumnType("boolean"); + + b.Property("IsQuestItem") + .HasColumnType("boolean"); + + b.Property("ItemSlotId") + .HasColumnType("uuid"); + + b.Property("MaximumDropLevel") + .HasColumnType("smallint"); + + b.Property("MaximumItemLevel") + .HasColumnType("smallint"); + + b.Property("MaximumSockets") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("PetExperienceFormula") + .HasColumnType("text"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.Property("StorageLimitPerCharacter") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("integer"); + + b.Property("Width") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ConsumeEffectId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("ItemSlotId"); + + b.HasIndex("SkillId"); + + b.ToTable("ItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionCharacterClass", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "CharacterClassId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("ItemDefinitionCharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemOptionDefinition", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemOptionDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "ItemOptionDefinitionId"); + + b.HasIndex("ItemOptionDefinitionId"); + + b.ToTable("ItemDefinitionItemOptionDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemSetGroup", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSetGroupId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "ItemSetGroupId"); + + b.HasIndex("ItemSetGroupId"); + + b.ToTable("ItemDefinitionItemSetGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Chance") + .HasColumnType("double precision"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DropEffect") + .HasColumnType("integer"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemLevel") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("integer"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MaximumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MinimumLevel") + .HasColumnType("smallint"); + + b.Property("MinimumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MoneyAmount") + .HasColumnType("integer"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.Property("RequiredCharacterLevel") + .HasColumnType("smallint"); + + b.Property("SourceItemLevel") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("MonsterId"); + + b.ToTable("ItemDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroupItemDefinition", b => + { + b.Property("ItemDropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemDropItemGroupId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("ItemDropItemGroupItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemItemOfItemSet", b => + { + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("ItemOfItemSetId") + .HasColumnType("uuid"); + + b.HasKey("ItemId", "ItemOfItemSetId"); + + b.HasIndex("ItemOfItemSetId"); + + b.ToTable("ItemItemOfItemSet", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemLevelBonusTable", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AncientSetDiscriminator") + .HasColumnType("integer"); + + b.Property("BonusOptionId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSetGroupId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BonusOptionId"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("ItemSetGroupId"); + + b.ToTable("ItemOfItemSet", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OptionTypeId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("ItemOption", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppliesMultipleTimes") + .HasColumnType("boolean"); + + b.Property("BonusId") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BonusId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionCombinationBonus", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddChance") + .HasColumnType("real"); + + b.Property("AddsRandomly") + .HasColumnType("boolean"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MaximumOptionsPerItem") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("ItemOptionId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemId"); + + b.HasIndex("ItemOptionId"); + + b.ToTable("ItemOptionLink", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IncreasableItemOptionId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("RequiredItemLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IncreasableItemOptionId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("ItemOptionOfLevel", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IsVisible") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlwaysApplies") + .HasColumnType("boolean"); + + b.Property("CountDistinct") + .HasColumnType("boolean"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MinimumItemCount") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionsId") + .HasColumnType("uuid"); + + b.Property("SetLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("OptionsId"); + + b.ToTable("ItemSetGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("RawItemSlots") + .HasColumnType("text") + .HasColumnName("ItemSlots") + .HasJsonPropertyName("itemSlots"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemSlotType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Money") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ItemStorage", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.JewelMix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MixedJewelId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SingleJewelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MixedJewelId"); + + b.HasIndex("SingleJewelId"); + + b.ToTable("JewelMix", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Animation") + .HasColumnType("smallint"); + + b.Property("HeaderId") + .HasColumnType("uuid"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("Rotation") + .HasColumnType("smallint"); + + b.Property("SenderAppearanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("HeaderId"); + + b.HasIndex("SenderAppearanceId") + .IsUnique(); + + b.ToTable("LetterBody", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("LetterDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadFlag") + .HasColumnType("boolean"); + + b.Property("ReceiverId") + .HasColumnType("uuid"); + + b.Property("SenderName") + .HasColumnType("text"); + + b.Property("Subject") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReceiverId"); + + b.ToTable("LetterHeader", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LevelBonus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalValue") + .HasColumnType("real"); + + b.Property("ItemLevelBonusTableId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemLevelBonusTableId"); + + b.ToTable("LevelBonus", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChanceId") + .HasColumnType("uuid"); + + b.Property("ChancePvpId") + .HasColumnType("uuid"); + + b.Property("DurationDependsOnTargetLevel") + .HasColumnType("boolean"); + + b.Property("DurationId") + .HasColumnType("uuid"); + + b.Property("DurationPvpId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("InformObservers") + .HasColumnType("boolean"); + + b.Property("MonsterTargetLevelDivisor") + .HasColumnType("real"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("PlayerTargetLevelDivisor") + .HasColumnType("real"); + + b.Property("SendDuration") + .HasColumnType("boolean"); + + b.Property("StopByDeath") + .HasColumnType("boolean"); + + b.Property("SubType") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ChanceId") + .IsUnique(); + + b.HasIndex("ChancePvpId") + .IsUnique(); + + b.HasIndex("DurationId") + .IsUnique(); + + b.HasIndex("DurationPvpId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("MagicEffectDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aggregation") + .HasColumnType("integer"); + + b.Property("DisplayValueFormula") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtendsDuration") + .HasColumnType("boolean"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MinimumLevel") + .HasColumnType("smallint"); + + b.Property("Rank") + .HasColumnType("smallint"); + + b.Property("ReplacedSkillId") + .HasColumnType("uuid"); + + b.Property("RootId") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.Property("ValueFormula") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReplacedSkillId"); + + b.HasIndex("RootId"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("MasterSkillDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinitionSkill", b => + { + b.Property("MasterSkillDefinitionId") + .HasColumnType("uuid"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("MasterSkillDefinitionId", "SkillId"); + + b.HasIndex("SkillId"); + + b.ToTable("MasterSkillDefinitionSkill", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("MasterSkillRoot", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("MinimumTargetLevel") + .HasColumnType("smallint"); + + b.Property("MultiplyKillsByPlayers") + .HasColumnType("boolean"); + + b.Property("NumberOfKills") + .HasColumnType("smallint"); + + b.Property("SpawnAreaId") + .HasColumnType("uuid"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TargetDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameDefinitionId"); + + b.HasIndex("SpawnAreaId") + .IsUnique(); + + b.HasIndex("TargetDefinitionId"); + + b.ToTable("MiniGameChangeEvent", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowParty") + .HasColumnType("boolean"); + + b.Property("ArePlayerKillersAllowedToEnter") + .HasColumnType("boolean"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnterDuration") + .HasColumnType("interval"); + + b.Property("EntranceFee") + .HasColumnType("integer"); + + b.Property("EntranceId") + .HasColumnType("uuid"); + + b.Property("ExitDuration") + .HasColumnType("interval"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("GameDuration") + .HasColumnType("interval"); + + b.Property("GameLevel") + .HasColumnType("smallint"); + + b.Property("MapCreationPolicy") + .HasColumnType("integer"); + + b.Property("MaximumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MaximumPlayerCount") + .HasColumnType("integer"); + + b.Property("MaximumSpecialCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumSpecialCharacterLevel") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequiresMasterClass") + .HasColumnType("boolean"); + + b.Property("SaveRankingStatistics") + .HasColumnType("boolean"); + + b.Property("TicketItemId") + .HasColumnType("uuid"); + + b.Property("TicketItemLevel") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("EntranceId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("TicketItemId"); + + b.ToTable("MiniGameDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameRankingEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("GameInstanceId") + .HasColumnType("uuid"); + + b.Property("MiniGameId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Score") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("MiniGameId"); + + b.ToTable("MiniGameRankingEntry", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemRewardId") + .HasColumnType("uuid"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("RequiredKillId") + .HasColumnType("uuid"); + + b.Property("RequiredSuccess") + .HasColumnType("integer"); + + b.Property("RewardAmount") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemRewardId"); + + b.HasIndex("MiniGameDefinitionId"); + + b.HasIndex("RequiredKillId"); + + b.ToTable("MiniGameReward", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameSpawnWave", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("WaveNumber") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameDefinitionId"); + + b.ToTable("MiniGameSpawnWave", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameTerrainChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EndX") + .HasColumnType("smallint"); + + b.Property("EndY") + .HasColumnType("smallint"); + + b.Property("IsClientUpdateRequired") + .HasColumnType("boolean"); + + b.Property("MiniGameChangeEventId") + .HasColumnType("uuid"); + + b.Property("SetTerrainAttribute") + .HasColumnType("boolean"); + + b.Property("StartX") + .HasColumnType("smallint"); + + b.Property("StartY") + .HasColumnType("smallint"); + + b.Property("TerrainAttribute") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameChangeEventId"); + + b.ToTable("MiniGameTerrainChange", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeDefinitionId") + .HasColumnType("uuid"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("AttributeDefinitionId"); + + b.HasIndex("MonsterDefinitionId"); + + b.ToTable("MonsterAttribute", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttackDelay") + .HasColumnType("interval"); + + b.Property("AttackRange") + .HasColumnType("smallint"); + + b.Property("AttackSkillId") + .HasColumnType("uuid"); + + b.Property("Attribute") + .HasColumnType("smallint"); + + b.Property("Designation") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IntelligenceTypeName") + .HasColumnType("text"); + + b.Property("MerchantStoreId") + .HasColumnType("uuid"); + + b.Property("MoveDelay") + .HasColumnType("interval"); + + b.Property("MoveRange") + .HasColumnType("smallint"); + + b.Property("NpcWindow") + .HasColumnType("integer"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("NumberOfMaximumItemDrops") + .HasColumnType("integer"); + + b.Property("ObjectKind") + .HasColumnType("integer"); + + b.Property("RespawnDelay") + .HasColumnType("interval"); + + b.Property("ViewRange") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AttackSkillId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MerchantStoreId") + .IsUnique(); + + b.ToTable("MonsterDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinitionDropItemGroup", b => + { + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("MonsterDefinitionId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("MonsterDefinitionDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("GameMapId") + .HasColumnType("uuid"); + + b.Property("MaximumHealthOverride") + .HasColumnType("integer"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasColumnType("smallint"); + + b.Property("SpawnTrigger") + .HasColumnType("integer"); + + b.Property("WaveNumber") + .HasColumnType("smallint"); + + 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("GameMapId"); + + b.HasIndex("MonsterDefinitionId"); + + b.ToTable("MonsterSpawnArea", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PlugInConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomConfiguration") + .HasColumnType("text"); + + b.Property("CustomPlugInSource") + .HasColumnType("text"); + + b.Property("ExternalAssemblyName") + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("TypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("PlugInConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BoostId") + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("MagicEffectDefinitionId") + .HasColumnType("uuid"); + + b.Property("MagicEffectDefinitionId1") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BoostId") + .IsUnique(); + + b.HasIndex("GameMapDefinitionId"); + + b.HasIndex("MagicEffectDefinitionId"); + + b.HasIndex("MagicEffectDefinitionId1"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("PowerUpDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AggregateType") + .HasColumnType("integer"); + + b.Property("MaximumValue") + .HasColumnType("real"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.ToTable("PowerUpDefinitionValue", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("MaximumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("QualifiedCharacterId") + .HasColumnType("uuid"); + + b.Property("QuestGiverId") + .HasColumnType("uuid"); + + b.Property("RefuseNumber") + .HasColumnType("smallint"); + + b.Property("Repeatable") + .HasColumnType("boolean"); + + b.Property("RequiredStartMoney") + .HasColumnType("integer"); + + b.Property("RequiresClientAction") + .HasColumnType("boolean"); + + b.Property("StartingNumber") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("MonsterDefinitionId"); + + b.HasIndex("QualifiedCharacterId"); + + b.HasIndex("QuestGiverId"); + + b.ToTable("QuestDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestItemRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("MinimumNumber") + .HasColumnType("integer"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DropItemGroupId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuestDefinitionId"); + + b.ToTable("QuestItemRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MinimumNumber") + .HasColumnType("integer"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonsterId"); + + b.HasIndex("QuestDefinitionId"); + + b.ToTable("QuestMonsterKillRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirementState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterQuestStateId") + .HasColumnType("uuid"); + + b.Property("KillCount") + .HasColumnType("integer"); + + b.Property("RequirementId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CharacterQuestStateId"); + + b.HasIndex("RequirementId"); + + b.ToTable("QuestMonsterKillRequirementState", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeRewardId") + .HasColumnType("uuid"); + + b.Property("ItemRewardId") + .HasColumnType("uuid"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("SkillRewardId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AttributeRewardId"); + + b.HasIndex("ItemRewardId") + .IsUnique(); + + b.HasIndex("QuestDefinitionId"); + + b.HasIndex("SkillRewardId"); + + b.ToTable("QuestReward", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .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.ToTable("Rectangle", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumSuccessPercent") + .HasColumnType("smallint"); + + b.Property("Money") + .HasColumnType("integer"); + + b.Property("MoneyPerFinalSuccessPercentage") + .HasColumnType("integer"); + + b.Property("MultipleAllowed") + .HasColumnType("boolean"); + + b.Property("NpcPriceDivisor") + .HasColumnType("integer"); + + b.Property("ResultItemExcellentOptionChance") + .HasColumnType("smallint"); + + b.Property("ResultItemLuckOptionChance") + .HasColumnType("smallint"); + + b.Property("ResultItemMaxExcOptionCount") + .HasColumnType("smallint"); + + b.Property("ResultItemSelect") + .HasColumnType("integer"); + + b.Property("ResultItemSkillChance") + .HasColumnType("smallint"); + + b.Property("SuccessPercent") + .HasColumnType("smallint"); + + b.Property("SuccessPercentageAdditionForAncientItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForExcellentItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForGuardianItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForLuck") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForSocketItem") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("SimpleCraftingSettings", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSkillSettingsId") + .HasColumnType("uuid"); + + b.Property("AttackDamage") + .HasColumnType("integer"); + + b.Property("DamageType") + .HasColumnType("integer"); + + b.Property("ElementalModifierTargetId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("ImplicitTargetRange") + .HasColumnType("smallint"); + + b.Property("MagicEffectDefId") + .HasColumnType("uuid"); + + b.Property("MasterDefinitionId") + .HasColumnType("uuid"); + + b.Property("MovesTarget") + .HasColumnType("boolean"); + + b.Property("MovesToTarget") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("NumberOfHitsPerAttack") + .HasColumnType("smallint"); + + b.Property("Range") + .HasColumnType("smallint"); + + b.Property("SkillType") + .HasColumnType("integer"); + + b.Property("SkipElementalModifier") + .HasColumnType("boolean"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TargetRestriction") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AreaSkillSettingsId") + .IsUnique(); + + b.HasIndex("ElementalModifierTargetId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MagicEffectDefId"); + + b.HasIndex("MasterDefinitionId") + .IsUnique(); + + b.ToTable("Skill", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillCharacterClass", b => + { + b.Property("SkillId") + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.HasKey("SkillId", "CharacterClassId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("SkillCharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumCompletionTime") + .HasColumnType("interval"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("SkillComboDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsFinalStep") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("SkillComboDefinitionId") + .HasColumnType("uuid"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SkillComboDefinitionId"); + + b.HasIndex("SkillId"); + + b.ToTable("SkillComboStep", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("SkillId"); + + b.ToTable("SkillEntry", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CharacterId"); + + b.HasIndex("DefinitionId"); + + b.ToTable("StatAttribute", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttributeDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeId") + .HasColumnType("uuid"); + + b.Property("BaseValue") + .HasColumnType("real"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.Property("IncreasableByPlayer") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AttributeId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("StatAttributeDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SystemConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AutoStart") + .HasColumnType("boolean"); + + b.Property("AutoUpdateSchema") + .HasColumnType("boolean"); + + b.Property("IpResolver") + .HasColumnType("integer"); + + b.Property("IpResolverParameter") + .HasColumnType("text"); + + b.Property("ReadConsoleInput") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("SystemConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.WarpInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Costs") + .HasColumnType("integer"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("GateId") + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("LevelRequirement") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("GateId"); + + b.ToTable("WarpInfo", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawVault") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", "VaultId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawVault"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AccountCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", "Account") + .WithMany("JoinedUnlockedCharacterClasses") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("CharacterClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawCharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId"); + + b.Navigation("RawCharacterClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawAttributes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRelationship", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", null) + .WithMany("RawAttributeCombinations") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawGlobalAttributeCombinations") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawInputAttribute") + .WithMany() + .HasForeignKey("InputAttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawOperandAttribute") + .WithMany() + .HasForeignKey("OperandAttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", null) + .WithMany("RawRelatedValues") + .HasForeignKey("PowerUpDefinitionValueId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawAttributeRelationships") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawInputAttribute"); + + b.Navigation("RawOperandAttribute"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttribute") + .WithMany() + .HasForeignKey("AttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawMapRequirements") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawRequirements") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawConsumeRequirements") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawRequirements") + .HasForeignKey("SkillId1") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawGround") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "GroundId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawLeftGoal") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "LeftGoalId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawRightGoal") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "RightGoalId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawGround"); + + b.Navigation("RawLeftGoal"); + + b.Navigation("RawRightGoal"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawMagicEffectDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", "MagicEffectDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawBuffs") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMagicEffectDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawAttackRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "AttackRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCastleSiegeMapDefinition") + .WithMany() + .HasForeignKey("CastleSiegeMapDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawDefenseRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "DefenseRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawLandOfTrialsMapDefinition") + .WithMany() + .HasForeignKey("LandOfTrialsMapDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawRewardItemDefinition") + .WithMany() + .HasForeignKey("RewardItemDefinitionId"); + + b.Navigation("RawAttackRespawnArea"); + + b.Navigation("RawCastleSiegeMapDefinition"); + + b.Navigation("RawDefenseRespawnArea"); + + b.Navigation("RawLandOfTrialsMapDefinition"); + + b.Navigation("RawRewardItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawNpcDefinitions") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition") + .WithMany() + .HasForeignKey("MonsterDefinitionId"); + + b.Navigation("RawMonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", null) + .WithMany("RawNpcStates") + .HasForeignKey("CastleSiegeDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStateSchedule") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId2") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId3") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueRegenUpgrades") + .HasForeignKey("CastleSiegeConfigurationId4") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawAttackMachineZones") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawDefenseMachineZones") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null) + .WithMany("RawCharacters") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawCharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCurrentMap") + .WithMany() + .HasForeignKey("CurrentMapId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawInventory") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "InventoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCharacterClass"); + + b.Navigation("RawCurrentMap"); + + b.Navigation("RawInventory"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", "RawComboDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "ComboDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawCharacterClasses") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawHomeMap") + .WithMany() + .HasForeignKey("HomeMapId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawNextGenerationClass") + .WithMany() + .HasForeignKey("NextGenerationClassId"); + + b.Navigation("RawComboDefinition"); + + b.Navigation("RawHomeMap"); + + b.Navigation("RawNextGenerationClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Character") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + + b.Navigation("DropItemGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", "RawActiveQuest") + .WithMany() + .HasForeignKey("ActiveQuestId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawQuestStates") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", "RawLastFinishedQuest") + .WithMany() + .HasForeignKey("LastFinishedQuestId"); + + b.Navigation("RawActiveQuest"); + + b.Navigation("RawLastFinishedQuest"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerEndpoint", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", null) + .WithMany("RawEndpoints") + .HasForeignKey("ChatServerDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CombinationBonusRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", null) + .WithMany("RawRequirements") + .HasForeignKey("ItemOptionCombinationBonusId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.Navigation("RawOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConnectServerDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConstValueAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany("RawBaseAttributeValues") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "GameConfiguration") + .WithMany("RawGlobalBaseAttributeValues") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("CharacterClass"); + + b.Navigation("GameConfiguration"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawDropItemGroups") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroupItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany("JoinedPossibleItems") + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelArea", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", null) + .WithMany("RawDuelAreas") + .HasForeignKey("DuelConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawFirstPlayerGate") + .WithMany() + .HasForeignKey("FirstPlayerGateId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawSecondPlayerGate") + .WithMany() + .HasForeignKey("SecondPlayerGateId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawSpectatorsGate") + .WithMany() + .HasForeignKey("SpectatorsGateId"); + + b.Navigation("RawFirstPlayerGate"); + + b.Navigation("RawSecondPlayerGate"); + + b.Navigation("RawSpectatorsGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawExit") + .WithMany() + .HasForeignKey("ExitId"); + + b.Navigation("RawExit"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.EnterGate", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawEnterGates") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawTargetGate") + .WithMany() + .HasForeignKey("TargetGateId"); + + b.Navigation("RawTargetGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawMap") + .WithMany("RawExitGates") + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMap"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "RawCastleSiegeConfiguration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", "RawDuelConfiguration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "DuelConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCastleSiegeConfiguration"); + + b.Navigation("RawDuelConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "RawBattleZone") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "BattleZoneId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMaps") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawSafezoneMap") + .WithMany() + .HasForeignKey("SafezoneMapId"); + + b.Navigation("RawBattleZone"); + + b.Navigation("RawSafezoneMap"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinitionDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "GameMapDefinition") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("GameMapDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfigurationGameMapDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "GameMapDefinition") + .WithMany() + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", "GameServerConfiguration") + .WithMany("JoinedMaps") + .HasForeignKey("GameServerConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GameMapDefinition"); + + b.Navigation("GameServerConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "RawGameConfiguration") + .WithMany() + .HasForeignKey("GameConfigurationId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", "RawServerConfiguration") + .WithMany() + .HasForeignKey("ServerConfigurationId"); + + b.Navigation("RawGameConfiguration"); + + b.Navigation("RawServerConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerEndpoint", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", null) + .WithMany("RawEndpoints") + .HasForeignKey("GameServerDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", "RawAllianceGuild") + .WithMany() + .HasForeignKey("AllianceGuildId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", "RawHostility") + .WithMany() + .HasForeignKey("HostilityId"); + + b.Navigation("RawAllianceGuild"); + + b.Navigation("RawHostility"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GuildMember", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany("RawMembers") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Character") + .WithMany() + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", null) + .WithMany("RawPossibleOptions") + .HasForeignKey("ItemOptionDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawOptionType"); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawItemStorage") + .WithMany("RawItems") + .HasForeignKey("ItemStorageId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawDefinition"); + + b.Navigation("RawItemStorage"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", null) + .WithMany("RawEquippedItems") + .HasForeignKey("AppearanceDataId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearanceItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", "ItemAppearance") + .WithMany("JoinedVisibleOptions") + .HasForeignKey("ItemAppearanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "ItemOptionType") + .WithMany() + .HasForeignKey("ItemOptionTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemAppearance"); + + b.Navigation("ItemOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemBasePowerUpDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", "RawBonusPerLevelTable") + .WithMany() + .HasForeignKey("BonusPerLevelTableId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawBasePowerUpAttributes") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawBonusPerLevelTable"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawItemCraftings") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", "RawSimpleCraftingSettings") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", "SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawSimpleCraftingSettings"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", null) + .WithMany("RawRequiredItems") + .HasForeignKey("SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", "ItemCraftingRequiredItem") + .WithMany("JoinedPossibleItems") + .HasForeignKey("ItemCraftingRequiredItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemCraftingRequiredItem"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", "ItemCraftingRequiredItem") + .WithMany("JoinedRequiredItemOptions") + .HasForeignKey("ItemCraftingRequiredItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "ItemOptionType") + .WithMany() + .HasForeignKey("ItemOptionTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemCraftingRequiredItem"); + + b.Navigation("ItemOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingResultItem", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", null) + .WithMany("RawResultItems") + .HasForeignKey("SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawConsumeEffect") + .WithMany() + .HasForeignKey("ConsumeEffectId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItems") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", "RawItemSlot") + .WithMany() + .HasForeignKey("ItemSlotId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawConsumeEffect"); + + b.Navigation("RawItemSlot"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedQualifiedCharacters") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CharacterClass"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemOptionDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedPossibleItemOptions") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", "ItemOptionDefinition") + .WithMany() + .HasForeignKey("ItemOptionDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemOptionDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemSetGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedPossibleItemSetGroups") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", "ItemSetGroup") + .WithMany() + .HasForeignKey("ItemSetGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemSetGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawDropItems") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroupItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", "ItemDropItemGroup") + .WithMany("JoinedPossibleItems") + .HasForeignKey("ItemDropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemDropItemGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemItemOfItemSet", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", "Item") + .WithMany("JoinedItemSetGroups") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", "ItemOfItemSet") + .WithMany() + .HasForeignKey("ItemOfItemSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ItemOfItemSet"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemLevelBonusTables") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "RawBonusOption") + .WithMany() + .HasForeignKey("BonusOptionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", "RawItemSetGroup") + .WithMany("RawItems") + .HasForeignKey("ItemSetGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawBonusOption"); + + b.Navigation("RawItemDefinition"); + + b.Navigation("RawItemSetGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawOptionType"); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawBonus") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", "BonusId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptionCombinationBonuses") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawBonus"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptions") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionLink", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", null) + .WithMany("RawItemOptions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "RawItemOption") + .WithMany() + .HasForeignKey("ItemOptionId"); + + b.Navigation("RawItemOption"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", null) + .WithMany("RawLevelDependentOptions") + .HasForeignKey("IncreasableItemOptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptionTypes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemSetGroups") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", "RawOptions") + .WithMany() + .HasForeignKey("OptionsId"); + + b.Navigation("RawOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemSlotTypes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.JewelMix", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawJewelMixes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawMixedJewel") + .WithMany() + .HasForeignKey("MixedJewelId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawSingleJewel") + .WithMany() + .HasForeignKey("SingleJewelId"); + + b.Navigation("RawMixedJewel"); + + b.Navigation("RawSingleJewel"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", "RawHeader") + .WithMany() + .HasForeignKey("HeaderId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", "RawSenderAppearance") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", "SenderAppearanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawHeader"); + + b.Navigation("RawSenderAppearance"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Receiver") + .WithMany("RawLetters") + .HasForeignKey("ReceiverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Receiver"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LevelBonus", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", null) + .WithMany("RawBonusPerLevel") + .HasForeignKey("ItemLevelBonusTableId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawChance") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "ChanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawChancePvp") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "ChancePvpId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawDuration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "DurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawDurationPvp") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "DurationPvpId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMagicEffects") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawChance"); + + b.Navigation("RawChancePvp"); + + b.Navigation("RawDuration"); + + b.Navigation("RawDurationPvp"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawReplacedSkill") + .WithMany() + .HasForeignKey("ReplacedSkillId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", "RawRoot") + .WithMany() + .HasForeignKey("RootId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawReplacedSkill"); + + b.Navigation("RawRoot"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinitionSkill", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", "MasterSkillDefinition") + .WithMany("JoinedRequiredMasterSkills") + .HasForeignKey("MasterSkillDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MasterSkillDefinition"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMasterSkillRoots") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawChangeEvents") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", "RawSpawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", "SpawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawTargetDefinition") + .WithMany() + .HasForeignKey("TargetDefinitionId"); + + b.Navigation("RawSpawnArea"); + + b.Navigation("RawTargetDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawEntrance") + .WithMany() + .HasForeignKey("EntranceId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMiniGameDefinitions") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawTicketItem") + .WithMany() + .HasForeignKey("TicketItemId"); + + b.Navigation("RawEntrance"); + + b.Navigation("RawTicketItem"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameRankingEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "RawCharacter") + .WithMany() + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", "RawMiniGame") + .WithMany() + .HasForeignKey("MiniGameId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCharacter"); + + b.Navigation("RawMiniGame"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameReward", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "RawItemReward") + .WithMany() + .HasForeignKey("ItemRewardId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawRewards") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawRequiredKill") + .WithMany() + .HasForeignKey("RequiredKillId"); + + b.Navigation("RawItemReward"); + + b.Navigation("RawRequiredKill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameSpawnWave", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawSpawnWaves") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameTerrainChange", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", null) + .WithMany("RawTerrainChanges") + .HasForeignKey("MiniGameChangeEventId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttributeDefinition") + .WithMany() + .HasForeignKey("AttributeDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawAttributes") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttributeDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawAttackSkill") + .WithMany() + .HasForeignKey("AttackSkillId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMonsters") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawMerchantStore") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "MerchantStoreId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttackSkill"); + + b.Navigation("RawMerchantStore"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinitionDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "MonsterDefinition") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("MonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawGameMap") + .WithMany("RawMonsterSpawns") + .HasForeignKey("GameMapId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition") + .WithMany() + .HasForeignKey("MonsterDefinitionId"); + + b.Navigation("RawGameMap"); + + b.Navigation("RawMonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PlugInConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawPlugInConfigurations") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawBoost") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "BoostId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawCharacterPowerUpDefinitions") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", null) + .WithMany("RawPowerUpDefinitions") + .HasForeignKey("MagicEffectDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", null) + .WithMany("RawPowerUpDefinitionsPvp") + .HasForeignKey("MagicEffectDefinitionId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_PowerUpDefinition_MagicEffectDefinition_MagicEffectDefinit~1"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawBoost"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawQuests") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawQualifiedCharacter") + .WithMany() + .HasForeignKey("QualifiedCharacterId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawQuestGiver") + .WithMany() + .HasForeignKey("QuestGiverId"); + + b.Navigation("RawQualifiedCharacter"); + + b.Navigation("RawQuestGiver"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestItemRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "RawDropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItem") + .WithMany() + .HasForeignKey("ItemId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRequiredItems") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawDropItemGroup"); + + b.Navigation("RawItem"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRequiredMonsterKills") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirementState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", null) + .WithMany("RawRequirementStates") + .HasForeignKey("CharacterQuestStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", "RawRequirement") + .WithMany() + .HasForeignKey("RequirementId"); + + b.Navigation("RawRequirement"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttributeReward") + .WithMany() + .HasForeignKey("AttributeRewardId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", "RawItemReward") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", "ItemRewardId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRewards") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkillReward") + .WithMany() + .HasForeignKey("SkillRewardId"); + + b.Navigation("RawAttributeReward"); + + b.Navigation("RawItemReward"); + + b.Navigation("RawSkillReward"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AreaSkillSettings", "RawAreaSkillSettings") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "AreaSkillSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawElementalModifierTarget") + .WithMany() + .HasForeignKey("ElementalModifierTargetId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawSkills") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawMagicEffectDef") + .WithMany() + .HasForeignKey("MagicEffectDefId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", "RawMasterDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "MasterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAreaSkillSettings"); + + b.Navigation("RawElementalModifierTarget"); + + b.Navigation("RawMagicEffectDef"); + + b.Navigation("RawMasterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "Skill") + .WithMany("JoinedQualifiedCharacters") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CharacterClass"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboStep", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", null) + .WithMany("RawSteps") + .HasForeignKey("SkillComboDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawLearnedSkills") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null) + .WithMany("RawAttributes") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawAttributes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttributeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttribute") + .WithMany() + .HasForeignKey("AttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", null) + .WithMany("RawStatAttributes") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.WarpInfo", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawWarpList") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawGate") + .WithMany() + .HasForeignKey("GateId"); + + b.Navigation("RawGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b => + { + b.Navigation("JoinedUnlockedCharacterClasses"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawCharacters"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b => + { + b.Navigation("RawEquippedItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.Navigation("RawAttackMachineZones"); + + b.Navigation("RawDefenseMachineZones"); + + b.Navigation("RawGateDefenseUpgrades"); + + b.Navigation("RawGateLifeUpgrades"); + + b.Navigation("RawNpcDefinitions"); + + b.Navigation("RawStateSchedule"); + + b.Navigation("RawStatueDefenseUpgrades"); + + b.Navigation("RawStatueLifeUpgrades"); + + b.Navigation("RawStatueRegenUpgrades"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b => + { + b.Navigation("RawNpcStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawLearnedSkills"); + + b.Navigation("RawLetters"); + + b.Navigation("RawQuestStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b => + { + b.Navigation("RawAttributeCombinations"); + + b.Navigation("RawBaseAttributeValues"); + + b.Navigation("RawStatAttributes"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.Navigation("RawRequirementStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", b => + { + b.Navigation("RawEndpoints"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.Navigation("JoinedPossibleItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.Navigation("RawDuelAreas"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.Navigation("RawAttributes"); + + b.Navigation("RawCharacterClasses"); + + b.Navigation("RawDropItemGroups"); + + b.Navigation("RawGlobalAttributeCombinations"); + + b.Navigation("RawGlobalBaseAttributeValues"); + + b.Navigation("RawItemLevelBonusTables"); + + b.Navigation("RawItemOptionCombinationBonuses"); + + b.Navigation("RawItemOptionTypes"); + + b.Navigation("RawItemOptions"); + + b.Navigation("RawItemSetGroups"); + + b.Navigation("RawItemSlotTypes"); + + b.Navigation("RawItems"); + + b.Navigation("RawJewelMixes"); + + b.Navigation("RawMagicEffects"); + + b.Navigation("RawMaps"); + + b.Navigation("RawMasterSkillRoots"); + + b.Navigation("RawMiniGameDefinitions"); + + b.Navigation("RawMonsters"); + + b.Navigation("RawPlugInConfigurations"); + + b.Navigation("RawSkills"); + + b.Navigation("RawWarpList"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawCharacterPowerUpDefinitions"); + + b.Navigation("RawEnterGates"); + + b.Navigation("RawExitGates"); + + b.Navigation("RawMapRequirements"); + + b.Navigation("RawMonsterSpawns"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", b => + { + b.Navigation("JoinedMaps"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.Navigation("RawEndpoints"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.Navigation("RawMembers"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.Navigation("RawLevelDependentOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.Navigation("JoinedItemSetGroups"); + + b.Navigation("RawItemOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.Navigation("JoinedVisibleOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.Navigation("JoinedPossibleItems"); + + b.Navigation("JoinedRequiredItemOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.Navigation("JoinedPossibleItemOptions"); + + b.Navigation("JoinedPossibleItemSetGroups"); + + b.Navigation("JoinedQualifiedCharacters"); + + b.Navigation("RawBasePowerUpAttributes"); + + b.Navigation("RawDropItems"); + + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.Navigation("JoinedPossibleItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.Navigation("RawBonusPerLevel"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.Navigation("RawPossibleOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.Navigation("RawItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", b => + { + b.Navigation("RawItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.Navigation("RawPowerUpDefinitions"); + + b.Navigation("RawPowerUpDefinitionsPvp"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.Navigation("JoinedRequiredMasterSkills"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.Navigation("RawTerrainChanges"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.Navigation("RawChangeEvents"); + + b.Navigation("RawRewards"); + + b.Navigation("RawSpawnWaves"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawBuffs"); + + b.Navigation("RawItemCraftings"); + + b.Navigation("RawQuests"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", b => + { + b.Navigation("RawRelatedValues"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.Navigation("RawRequiredItems"); + + b.Navigation("RawRequiredMonsterKills"); + + b.Navigation("RawRewards"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", b => + { + b.Navigation("RawRequiredItems"); + + b.Navigation("RawResultItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.Navigation("JoinedQualifiedCharacters"); + + b.Navigation("RawAttributeRelationships"); + + b.Navigation("RawConsumeRequirements"); + + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", b => + { + b.Navigation("RawSteps"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.cs b/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.cs new file mode 100644 index 0000000..d1bceb0 --- /dev/null +++ b/src/Persistence/EntityFramework/Migrations/20260730194321_AddCastleSiege.cs @@ -0,0 +1,453 @@ +// +// 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 AddCastleSiege : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CastleSiegeConfigurationId", + schema: "config", + table: "GameConfiguration", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "CastleSiegeData", + schema: "data", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OwnerGuildId = table.Column(type: "uuid", nullable: true), + IsOccupied = table.Column(type: "boolean", nullable: false), + TaxChaos = table.Column(type: "smallint", nullable: false), + TaxStore = table.Column(type: "smallint", nullable: false), + TaxHunt = table.Column(type: "integer", nullable: false), + IsHuntZoneEnabled = table.Column(type: "boolean", nullable: false), + TributeMoney = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeData", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeNpcState", + schema: "data", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CastleSiegeDataId = table.Column(type: "uuid", nullable: true), + MonsterNumber = table.Column(type: "smallint", nullable: false), + InstanceId = table.Column(type: "smallint", nullable: false), + DefenseLevel = table.Column(type: "smallint", nullable: false), + RegenLevel = table.Column(type: "smallint", nullable: false), + LifeLevel = table.Column(type: "smallint", nullable: false), + CurrentHp = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeNpcState", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeNpcState_CastleSiegeData_CastleSiegeDataId", + column: x => x.CastleSiegeDataId, + principalSchema: "data", + principalTable: "CastleSiegeData", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeConfiguration", + schema: "config", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CastleSiegeMapDefinitionId = table.Column(type: "uuid", nullable: true), + LandOfTrialsMapDefinitionId = table.Column(type: "uuid", nullable: true), + RewardItemDefinitionId = table.Column(type: "uuid", nullable: true), + DefenseRespawnAreaId = table.Column(type: "uuid", nullable: true), + AttackRespawnAreaId = table.Column(type: "uuid", nullable: true), + Enabled = table.Column(type: "boolean", nullable: false), + CrownHoldTimeSeconds = table.Column(type: "integer", nullable: false), + RegisterMinLevel = table.Column(type: "integer", nullable: false), + RegisterMinMembers = table.Column(type: "integer", nullable: false), + ParticipantRewardMinSeconds = table.Column(type: "integer", nullable: false), + MaxAttackingGuilds = table.Column(type: "integer", nullable: false), + GuildScoreCastleSiege = table.Column(type: "integer", nullable: false), + GuildScoreCastleSiegeMembers = table.Column(type: "integer", nullable: false), + GateBuyPrice = table.Column(type: "integer", nullable: false), + StatueBuyPrice = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeConfiguration", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~", + column: x => x.CastleSiegeMapDefinitionId, + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~", + column: x => x.LandOfTrialsMapDefinitionId, + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~", + column: x => x.RewardItemDefinitionId, + principalSchema: "config", + principalTable: "ItemDefinition", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeNpcDefinition", + schema: "config", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + MonsterDefinitionId = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId = table.Column(type: "uuid", nullable: true), + InstanceId = table.Column(type: "smallint", nullable: false), + IsPersistedToDatabase = table.Column(type: "boolean", nullable: false), + DefaultSide = table.Column(type: "smallint", nullable: false), + SpawnX = table.Column(type: "smallint", nullable: false), + SpawnY = table.Column(type: "smallint", nullable: false), + Direction = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeNpcDefinition", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeNpcDefinition_CastleSiegeConfiguration_CastleSie~", + column: x => x.CastleSiegeConfigurationId, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~", + column: x => x.MonsterDefinitionId, + principalSchema: "config", + principalTable: "MonsterDefinition", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeStateScheduleEntry", + schema: "config", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CastleSiegeConfigurationId = table.Column(type: "uuid", nullable: true), + State = table.Column(type: "smallint", nullable: false), + DayOfWeek = table.Column(type: "integer", nullable: false), + Hour = table.Column(type: "smallint", nullable: false), + Minute = table.Column(type: "smallint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeStateScheduleEntry", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeStateScheduleEntry_CastleSiegeConfiguration_Cast~", + column: x => x.CastleSiegeConfigurationId, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeUpgradeDefinition", + schema: "config", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CastleSiegeConfigurationId = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId1 = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId2 = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId3 = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId4 = table.Column(type: "uuid", nullable: true), + Level = table.Column(type: "smallint", nullable: false), + RequiredJewelOfGuardianCount = table.Column(type: "integer", nullable: false), + RequiredZen = table.Column(type: "integer", nullable: false), + Value = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeUpgradeDefinition", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Castl~", + column: x => x.CastleSiegeConfigurationId, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1", + column: x => x.CastleSiegeConfigurationId1, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2", + column: x => x.CastleSiegeConfigurationId2, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3", + column: x => x.CastleSiegeConfigurationId3, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4", + column: x => x.CastleSiegeConfigurationId4, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CastleSiegeZoneDefinition", + schema: "config", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CastleSiegeConfigurationId = table.Column(type: "uuid", nullable: true), + CastleSiegeConfigurationId1 = table.Column(type: "uuid", nullable: true), + X1 = table.Column(type: "smallint", nullable: false), + Y1 = table.Column(type: "smallint", nullable: false), + X2 = table.Column(type: "smallint", nullable: false), + Y2 = table.Column(type: "smallint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeZoneDefinition", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleSi~", + column: x => x.CastleSiegeConfigurationId, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1", + column: x => x.CastleSiegeConfigurationId1, + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_GameConfiguration_CastleSiegeConfigurationId", + schema: "config", + table: "GameConfiguration", + column: "CastleSiegeConfigurationId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeConfiguration_AttackRespawnAreaId", + schema: "config", + table: "CastleSiegeConfiguration", + column: "AttackRespawnAreaId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeConfiguration_CastleSiegeMapDefinitionId", + schema: "config", + table: "CastleSiegeConfiguration", + column: "CastleSiegeMapDefinitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeConfiguration_DefenseRespawnAreaId", + schema: "config", + table: "CastleSiegeConfiguration", + column: "DefenseRespawnAreaId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeConfiguration_LandOfTrialsMapDefinitionId", + schema: "config", + table: "CastleSiegeConfiguration", + column: "LandOfTrialsMapDefinitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeConfiguration_RewardItemDefinitionId", + schema: "config", + table: "CastleSiegeConfiguration", + column: "RewardItemDefinitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcDefinition_CastleSiegeConfigurationId", + schema: "config", + table: "CastleSiegeNpcDefinition", + column: "CastleSiegeConfigurationId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId", + schema: "config", + table: "CastleSiegeNpcDefinition", + column: "MonsterDefinitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcState_CastleSiegeDataId", + schema: "data", + table: "CastleSiegeNpcState", + column: "CastleSiegeDataId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeStateScheduleEntry_CastleSiegeConfigurationId", + schema: "config", + table: "CastleSiegeStateScheduleEntry", + column: "CastleSiegeConfigurationId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId", + schema: "config", + table: "CastleSiegeUpgradeDefinition", + column: "CastleSiegeConfigurationId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId1", + schema: "config", + table: "CastleSiegeUpgradeDefinition", + column: "CastleSiegeConfigurationId1"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId2", + schema: "config", + table: "CastleSiegeUpgradeDefinition", + column: "CastleSiegeConfigurationId2"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId3", + schema: "config", + table: "CastleSiegeUpgradeDefinition", + column: "CastleSiegeConfigurationId3"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeUpgradeDefinition_CastleSiegeConfigurationId4", + schema: "config", + table: "CastleSiegeUpgradeDefinition", + column: "CastleSiegeConfigurationId4"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeZoneDefinition_CastleSiegeConfigurationId", + schema: "config", + table: "CastleSiegeZoneDefinition", + column: "CastleSiegeConfigurationId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeZoneDefinition_CastleSiegeConfigurationId1", + schema: "config", + table: "CastleSiegeZoneDefinition", + column: "CastleSiegeConfigurationId1"); + + migrationBuilder.AddForeignKey( + name: "FK_GameConfiguration_CastleSiegeConfiguration_CastleSiegeConfi~", + schema: "config", + table: "GameConfiguration", + column: "CastleSiegeConfigurationId", + principalSchema: "config", + principalTable: "CastleSiegeConfiguration", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_AttackRe~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "AttackRespawnAreaId", + principalSchema: "config", + principalTable: "CastleSiegeZoneDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_DefenseR~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "DefenseRespawnAreaId", + principalSchema: "config", + principalTable: "CastleSiegeZoneDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_GameConfiguration_CastleSiegeConfiguration_CastleSiegeConfi~", + schema: "config", + table: "GameConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_AttackRe~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_CastleSiegeZoneDefinition_DefenseR~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropTable( + name: "CastleSiegeNpcDefinition", + schema: "config"); + + migrationBuilder.DropTable( + name: "CastleSiegeNpcState", + schema: "data"); + + migrationBuilder.DropTable( + name: "CastleSiegeStateScheduleEntry", + schema: "config"); + + migrationBuilder.DropTable( + name: "CastleSiegeUpgradeDefinition", + schema: "config"); + + migrationBuilder.DropTable( + name: "CastleSiegeData", + schema: "data"); + + migrationBuilder.DropTable( + name: "CastleSiegeZoneDefinition", + schema: "config"); + + migrationBuilder.DropTable( + name: "CastleSiegeConfiguration", + schema: "config"); + + migrationBuilder.DropIndex( + name: "IX_GameConfiguration_CastleSiegeConfigurationId", + schema: "config", + table: "GameConfiguration"); + + migrationBuilder.DropColumn( + name: "CastleSiegeConfigurationId", + schema: "config", + table: "GameConfiguration"); + } + } +} diff --git a/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs b/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs new file mode 100644 index 0000000..4e8288b --- /dev/null +++ b/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.Designer.cs @@ -0,0 +1,5788 @@ +// +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("20260801162427_ConfigureCastleSiegePersistence")] + partial class ConfigureCastleSiegePersistence + { + /// + 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") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30); + + 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") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(3); + + b.Property("ParticipantRewardMinSeconds") + .HasColumnType("integer"); + + b.Property("RegisterMinLevel") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(200); + + b.Property("RegisterMinMembers") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + 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.HasIndex("OwnerGuildId"); + + b.ToTable("CastleSiegeData", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("GuildName") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Marks") + .HasColumnType("integer"); + + b.Property("RegistrationOrder") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("CastleSiegeGuildRegistration", "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", "InstanceId"); + + 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.HasIndex("MonsterNumber", "InstanceId") + .IsUnique(); + + 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("CanGetCreated") + .HasColumnType("boolean"); + + b.Property("ComboDefinitionId") + .HasColumnType("uuid"); + + b.Property("CreationAllowedFlag") + .HasColumnType("smallint"); + + b.Property("FruitCalculation") + .HasColumnType("integer"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("HomeMapId") + .HasColumnType("uuid"); + + b.Property("IsMasterClass") + .HasColumnType("boolean"); + + b.Property("LevelRequirementByCreation") + .HasColumnType("smallint"); + + b.Property("LevelWarpRequirementReductionPercent") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NextGenerationClassId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ComboDefinitionId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("HomeMapId"); + + b.HasIndex("NextGenerationClassId"); + + b.ToTable("CharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterDropItemGroup", b => + { + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("CharacterId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("CharacterDropItemGroup", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveQuestId") + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("ClientActionPerformed") + .HasColumnType("boolean"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("LastFinishedQuestId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ActiveQuestId"); + + b.HasIndex("CharacterId"); + + b.HasIndex("LastFinishedQuestId"); + + b.ToTable("CharacterQuestState", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientCleanUpInterval") + .HasColumnType("interval"); + + b.Property("ClientTimeout") + .HasColumnType("interval"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("MaximumConnections") + .HasColumnType("integer"); + + b.Property("RoomCleanUpInterval") + .HasColumnType("interval"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("ChatServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChatServerDefinitionId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("NetworkPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChatServerDefinitionId"); + + b.HasIndex("ClientId"); + + b.ToTable("ChatServerEndpoint", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CombinationBonusRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemOptionCombinationBonusId") + .HasColumnType("uuid"); + + b.Property("MinimumCount") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemOptionCombinationBonusId"); + + b.HasIndex("OptionTypeId"); + + b.ToTable("CombinationBonusRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ConfigurationUpdate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdateState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrentInstalledVersion") + .HasColumnType("integer"); + + b.Property("InitializationKey") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ConfigurationUpdateState", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConnectServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CheckMaxConnectionsPerAddress") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientListenerPort") + .HasColumnType("integer"); + + b.Property("CurrentPatchVersion") + .HasColumnType("bytea"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisconnectOnUnknownPacket") + .HasColumnType("boolean"); + + b.Property("ListenerBacklog") + .HasColumnType("integer"); + + b.Property("MaxConnections") + .HasColumnType("integer"); + + b.Property("MaxConnectionsPerAddress") + .HasColumnType("integer"); + + b.Property("MaxFtpRequests") + .HasColumnType("integer"); + + b.Property("MaxIpRequests") + .HasColumnType("integer"); + + b.Property("MaxServerListRequests") + .HasColumnType("integer"); + + b.Property("MaximumReceiveSize") + .HasColumnType("smallint"); + + b.Property("PatchAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.Property("Timeout") + .HasColumnType("interval"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ConnectServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConstValueAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("CharacterClassId"); + + b.HasIndex("DefinitionId"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ConstValueAttribute", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Chance") + .HasColumnType("double precision"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("ItemLevel") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("integer"); + + b.Property("MaximumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MinimumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MonsterId"); + + b.ToTable("DropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroupItemDefinition", b => + { + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("DropItemGroupId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("DropItemGroupItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DuelConfigurationId") + .HasColumnType("uuid"); + + b.Property("FirstPlayerGateId") + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("smallint"); + + b.Property("SecondPlayerGateId") + .HasColumnType("uuid"); + + b.Property("SpectatorsGateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DuelConfigurationId"); + + b.HasIndex("FirstPlayerGateId"); + + b.HasIndex("SecondPlayerGateId"); + + b.HasIndex("SpectatorsGateId"); + + b.ToTable("DuelArea", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EntranceFee") + .HasColumnType("integer"); + + b.Property("ExitId") + .HasColumnType("uuid"); + + b.Property("MaximumScore") + .HasColumnType("integer"); + + b.Property("MaximumSpectatorsPerDuelRoom") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ExitId"); + + b.ToTable("DuelConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.EnterGate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("LevelRequirement") + .HasColumnType("smallint"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("TargetGateId") + .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("GameMapDefinitionId"); + + b.HasIndex("TargetGateId"); + + b.ToTable("EnterGate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("IsSpawnGate") + .HasColumnType("boolean"); + + b.Property("MapId") + .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("MapId"); + + b.ToTable("ExitGate", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Accepted") + .HasColumnType("boolean"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("FriendId") + .HasColumnType("uuid"); + + b.Property("RequestOpen") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasAlternateKey("CharacterId", "FriendId"); + + b.ToTable("Friend", "friend"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Episode") + .HasColumnType("smallint"); + + b.Property("Language") + .HasColumnType("integer"); + + b.Property("Season") + .HasColumnType("smallint"); + + b.Property("Serial") + .HasColumnType("bytea"); + + b.Property("Version") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.ToTable("GameClientDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSkillHitsPlayer") + .HasColumnType("boolean"); + + b.Property("CastleSiegeConfigurationId") + .HasColumnType("uuid"); + + b.Property("CharacterNameRegex") + .HasColumnType("text"); + + b.Property("ClampMoneyOnPickup") + .HasColumnType("boolean"); + + b.Property("DamagePerOneItemDurability") + .HasColumnType("double precision"); + + b.Property("DamagePerOnePetDurability") + .HasColumnType("double precision"); + + b.Property("DuelConfigurationId") + .HasColumnType("uuid"); + + b.Property("ExcellentItemDropLevelDelta") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((byte)25); + + b.Property("ExperienceFormula") + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("if(level == 0, 0, if(level < 256, 10 * (level + 8) * (level - 1) * (level - 1), (10 * (level + 8) * (level - 1) * (level - 1)) + (1000 * (level - 247) * (level - 256) * (level - 256))))"); + + b.Property("ExperienceRate") + .HasColumnType("real"); + + b.Property("HitsPerOneItemDurability") + .HasColumnType("double precision"); + + b.Property("InfoRange") + .HasColumnType("smallint"); + + b.Property("ItemDropDuration") + .ValueGeneratedOnAdd() + .HasColumnType("interval") + .HasDefaultValue(new TimeSpan(0, 0, 1, 0, 0)); + + b.Property("LetterSendPrice") + .HasColumnType("integer"); + + b.Property("MasterExperienceFormula") + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("(505 * level * level * level) + (35278500 * level) + (228045 * level * level)"); + + b.Property("MasterExperienceRate") + .HasColumnType("real"); + + b.Property("MaximumCharactersPerAccount") + .HasColumnType("smallint"); + + b.Property("MaximumInventoryMoney") + .HasColumnType("integer"); + + b.Property("MaximumItemOptionLevelDrop") + .HasColumnType("smallint"); + + b.Property("MaximumLetters") + .HasColumnType("integer"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MaximumMasterLevel") + .HasColumnType("smallint"); + + b.Property("MaximumPartySize") + .HasColumnType("smallint"); + + b.Property("MaximumPasswordLength") + .HasColumnType("integer"); + + b.Property("MaximumVaultMoney") + .HasColumnType("integer"); + + b.Property("MinimumMonsterLevelForMasterExperience") + .HasColumnType("smallint"); + + b.Property("PreventExperienceOverflow") + .HasColumnType("boolean"); + + b.Property("RecoveryInterval") + .HasColumnType("integer"); + + b.Property("ShouldDropMoney") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CastleSiegeConfigurationId") + .IsUnique(); + + b.HasIndex("DuelConfigurationId") + .IsUnique(); + + b.ToTable("GameConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BattleZoneId") + .HasColumnType("uuid"); + + b.Property("Discriminator") + .HasColumnType("integer"); + + b.Property("ExpMultiplier") + .HasColumnType("double precision"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SafezoneMapId") + .HasColumnType("uuid"); + + b.Property("TerrainData") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("BattleZoneId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("SafezoneMapId"); + + b.ToTable("GameMapDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinitionDropItemGroup", b => + { + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("GameMapDefinitionId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("GameMapDefinitionDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumPlayers") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("GameServerConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfigurationGameMapDefinition", b => + { + b.Property("GameServerConfigurationId") + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("GameServerConfigurationId", "GameMapDefinitionId"); + + b.HasIndex("GameMapDefinitionId"); + + b.ToTable("GameServerConfigurationGameMapDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExperienceRate") + .HasColumnType("real"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("PvpEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ServerConfigurationId") + .HasColumnType("uuid"); + + b.Property("ServerID") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("ServerConfigurationId"); + + b.ToTable("GameServerDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlternativePublishedPort") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("GameServerDefinitionId") + .HasColumnType("uuid"); + + b.Property("NetworkPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("GameServerDefinitionId"); + + b.ToTable("GameServerEndpoint", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllianceGuildId") + .HasColumnType("uuid"); + + b.Property("HostilityId") + .HasColumnType("uuid"); + + b.Property("Logo") + .HasColumnType("bytea"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Notice") + .HasColumnType("text"); + + b.Property("Score") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AllianceGuildId"); + + b.HasIndex("HostilityId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Guild", "guild"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GuildMember", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildMember", "guild"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemOptionDefinitionId") + .HasColumnType("uuid"); + + b.Property("LevelType") + .HasColumnType("integer"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.Property("Weight") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ItemOptionDefinitionId"); + + b.HasIndex("OptionTypeId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("IncreasableItemOption", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("Durability") + .HasColumnType("double precision"); + + b.Property("HasSkill") + .HasColumnType("boolean"); + + b.Property("ItemSlot") + .HasColumnType("smallint"); + + b.Property("ItemStorageId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("PetExperience") + .HasColumnType("integer"); + + b.Property("SocketCount") + .HasColumnType("integer"); + + b.Property("StorePrice") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DefinitionId"); + + b.HasIndex("ItemStorageId"); + + b.ToTable("Item", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppearanceDataId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSlot") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AppearanceDataId"); + + b.HasIndex("DefinitionId"); + + b.ToTable("ItemAppearance", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearanceItemOptionType", b => + { + b.Property("ItemAppearanceId") + .HasColumnType("uuid"); + + b.Property("ItemOptionTypeId") + .HasColumnType("uuid"); + + b.HasKey("ItemAppearanceId", "ItemOptionTypeId"); + + b.HasIndex("ItemOptionTypeId"); + + b.ToTable("ItemAppearanceItemOptionType", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemBasePowerUpDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AggregateType") + .HasColumnType("integer"); + + b.Property("BaseValue") + .HasColumnType("real"); + + b.Property("BonusPerLevelTableId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BonusPerLevelTableId"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("ItemBasePowerUpDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemCraftingHandlerClassName") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonsterDefinitionId"); + + b.HasIndex("SimpleCraftingSettingsId") + .IsUnique(); + + b.ToTable("ItemCrafting", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddPercentage") + .HasColumnType("smallint"); + + b.Property("FailResult") + .HasColumnType("integer"); + + b.Property("MaximumAmount") + .HasColumnType("smallint"); + + b.Property("MaximumItemLevel") + .HasColumnType("smallint"); + + b.Property("MinimumAmount") + .HasColumnType("smallint"); + + b.Property("MinimumItemLevel") + .HasColumnType("smallint"); + + b.Property("NpcPriceDivisor") + .HasColumnType("integer"); + + b.Property("Reference") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.Property("SuccessResult") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SimpleCraftingSettingsId"); + + b.ToTable("ItemCraftingRequiredItem", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemDefinition", b => + { + b.Property("ItemCraftingRequiredItemId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemCraftingRequiredItemId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("ItemCraftingRequiredItemItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemOptionType", b => + { + b.Property("ItemCraftingRequiredItemId") + .HasColumnType("uuid"); + + b.Property("ItemOptionTypeId") + .HasColumnType("uuid"); + + b.HasKey("ItemCraftingRequiredItemId", "ItemOptionTypeId"); + + b.HasIndex("ItemOptionTypeId"); + + b.ToTable("ItemCraftingRequiredItemItemOptionType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingResultItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddLevel") + .HasColumnType("smallint"); + + b.Property("Durability") + .HasColumnType("smallint"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("RandomMaximumLevel") + .HasColumnType("smallint"); + + b.Property("RandomMinimumLevel") + .HasColumnType("smallint"); + + b.Property("Reference") + .HasColumnType("smallint"); + + b.Property("SimpleCraftingSettingsId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("SimpleCraftingSettingsId"); + + b.ToTable("ItemCraftingResultItem", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConsumeEffectId") + .HasColumnType("uuid"); + + b.Property("DropLevel") + .HasColumnType("smallint"); + + b.Property("DropsFromMonsters") + .HasColumnType("boolean"); + + b.Property("Durability") + .HasColumnType("smallint"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("Height") + .HasColumnType("smallint"); + + b.Property("IsAmmunition") + .HasColumnType("boolean"); + + b.Property("IsBoundToCharacter") + .HasColumnType("boolean"); + + b.Property("IsQuestItem") + .HasColumnType("boolean"); + + b.Property("ItemSlotId") + .HasColumnType("uuid"); + + b.Property("MaximumDropLevel") + .HasColumnType("smallint"); + + b.Property("MaximumItemLevel") + .HasColumnType("smallint"); + + b.Property("MaximumSockets") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("PetExperienceFormula") + .HasColumnType("text"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.Property("StorageLimitPerCharacter") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("integer"); + + b.Property("Width") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ConsumeEffectId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("ItemSlotId"); + + b.HasIndex("SkillId"); + + b.ToTable("ItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionCharacterClass", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "CharacterClassId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("ItemDefinitionCharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemOptionDefinition", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemOptionDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "ItemOptionDefinitionId"); + + b.HasIndex("ItemOptionDefinitionId"); + + b.ToTable("ItemDefinitionItemOptionDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemSetGroup", b => + { + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSetGroupId") + .HasColumnType("uuid"); + + b.HasKey("ItemDefinitionId", "ItemSetGroupId"); + + b.HasIndex("ItemSetGroupId"); + + b.ToTable("ItemDefinitionItemSetGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Chance") + .HasColumnType("double precision"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DropEffect") + .HasColumnType("integer"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemLevel") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("integer"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MaximumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MinimumLevel") + .HasColumnType("smallint"); + + b.Property("MinimumMonsterLevel") + .HasColumnType("smallint"); + + b.Property("MoneyAmount") + .HasColumnType("integer"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.Property("RequiredCharacterLevel") + .HasColumnType("smallint"); + + b.Property("SourceItemLevel") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("MonsterId"); + + b.ToTable("ItemDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroupItemDefinition", b => + { + b.Property("ItemDropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("ItemDropItemGroupId", "ItemDefinitionId"); + + b.HasIndex("ItemDefinitionId"); + + b.ToTable("ItemDropItemGroupItemDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemItemOfItemSet", b => + { + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("ItemOfItemSetId") + .HasColumnType("uuid"); + + b.HasKey("ItemId", "ItemOfItemSetId"); + + b.HasIndex("ItemOfItemSetId"); + + b.ToTable("ItemItemOfItemSet", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemLevelBonusTable", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AncientSetDiscriminator") + .HasColumnType("integer"); + + b.Property("BonusOptionId") + .HasColumnType("uuid"); + + b.Property("ItemDefinitionId") + .HasColumnType("uuid"); + + b.Property("ItemSetGroupId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BonusOptionId"); + + b.HasIndex("ItemDefinitionId"); + + b.HasIndex("ItemSetGroupId"); + + b.ToTable("ItemOfItemSet", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("OptionTypeId") + .HasColumnType("uuid"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("SubOptionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OptionTypeId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("ItemOption", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppliesMultipleTimes") + .HasColumnType("boolean"); + + b.Property("BonusId") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BonusId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionCombinationBonus", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddChance") + .HasColumnType("real"); + + b.Property("AddsRandomly") + .HasColumnType("boolean"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MaximumOptionsPerItem") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("ItemOptionId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemId"); + + b.HasIndex("ItemOptionId"); + + b.ToTable("ItemOptionLink", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IncreasableItemOptionId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("PowerUpDefinitionId") + .HasColumnType("uuid"); + + b.Property("RequiredItemLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IncreasableItemOptionId"); + + b.HasIndex("PowerUpDefinitionId") + .IsUnique(); + + b.ToTable("ItemOptionOfLevel", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IsVisible") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemOptionType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlwaysApplies") + .HasColumnType("boolean"); + + b.Property("CountDistinct") + .HasColumnType("boolean"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MinimumItemCount") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionsId") + .HasColumnType("uuid"); + + b.Property("SetLevel") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("OptionsId"); + + b.ToTable("ItemSetGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("RawItemSlots") + .HasColumnType("text") + .HasColumnName("ItemSlots") + .HasJsonPropertyName("itemSlots"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("ItemSlotType", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Money") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ItemStorage", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.JewelMix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("MixedJewelId") + .HasColumnType("uuid"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("SingleJewelId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MixedJewelId"); + + b.HasIndex("SingleJewelId"); + + b.ToTable("JewelMix", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Animation") + .HasColumnType("smallint"); + + b.Property("HeaderId") + .HasColumnType("uuid"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("Rotation") + .HasColumnType("smallint"); + + b.Property("SenderAppearanceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("HeaderId"); + + b.HasIndex("SenderAppearanceId") + .IsUnique(); + + b.ToTable("LetterBody", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("LetterDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadFlag") + .HasColumnType("boolean"); + + b.Property("ReceiverId") + .HasColumnType("uuid"); + + b.Property("SenderName") + .HasColumnType("text"); + + b.Property("Subject") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReceiverId"); + + b.ToTable("LetterHeader", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LevelBonus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalValue") + .HasColumnType("real"); + + b.Property("ItemLevelBonusTableId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemLevelBonusTableId"); + + b.ToTable("LevelBonus", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChanceId") + .HasColumnType("uuid"); + + b.Property("ChancePvpId") + .HasColumnType("uuid"); + + b.Property("DurationDependsOnTargetLevel") + .HasColumnType("boolean"); + + b.Property("DurationId") + .HasColumnType("uuid"); + + b.Property("DurationPvpId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("InformObservers") + .HasColumnType("boolean"); + + b.Property("MonsterTargetLevelDivisor") + .HasColumnType("real"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("PlayerTargetLevelDivisor") + .HasColumnType("real"); + + b.Property("SendDuration") + .HasColumnType("boolean"); + + b.Property("StopByDeath") + .HasColumnType("boolean"); + + b.Property("SubType") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ChanceId") + .IsUnique(); + + b.HasIndex("ChancePvpId") + .IsUnique(); + + b.HasIndex("DurationId") + .IsUnique(); + + b.HasIndex("DurationPvpId") + .IsUnique(); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("MagicEffectDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aggregation") + .HasColumnType("integer"); + + b.Property("DisplayValueFormula") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExtendsDuration") + .HasColumnType("boolean"); + + b.Property("MaximumLevel") + .HasColumnType("smallint"); + + b.Property("MinimumLevel") + .HasColumnType("smallint"); + + b.Property("Rank") + .HasColumnType("smallint"); + + b.Property("ReplacedSkillId") + .HasColumnType("uuid"); + + b.Property("RootId") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.Property("ValueFormula") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReplacedSkillId"); + + b.HasIndex("RootId"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("MasterSkillDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinitionSkill", b => + { + b.Property("MasterSkillDefinitionId") + .HasColumnType("uuid"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("MasterSkillDefinitionId", "SkillId"); + + b.HasIndex("SkillId"); + + b.ToTable("MasterSkillDefinitionSkill", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("MasterSkillRoot", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("MinimumTargetLevel") + .HasColumnType("smallint"); + + b.Property("MultiplyKillsByPlayers") + .HasColumnType("boolean"); + + b.Property("NumberOfKills") + .HasColumnType("smallint"); + + b.Property("SpawnAreaId") + .HasColumnType("uuid"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TargetDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameDefinitionId"); + + b.HasIndex("SpawnAreaId") + .IsUnique(); + + b.HasIndex("TargetDefinitionId"); + + b.ToTable("MiniGameChangeEvent", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowParty") + .HasColumnType("boolean"); + + b.Property("ArePlayerKillersAllowedToEnter") + .HasColumnType("boolean"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnterDuration") + .HasColumnType("interval"); + + b.Property("EntranceFee") + .HasColumnType("integer"); + + b.Property("EntranceId") + .HasColumnType("uuid"); + + b.Property("ExitDuration") + .HasColumnType("interval"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("GameDuration") + .HasColumnType("interval"); + + b.Property("GameLevel") + .HasColumnType("smallint"); + + b.Property("MapCreationPolicy") + .HasColumnType("integer"); + + b.Property("MaximumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MaximumPlayerCount") + .HasColumnType("integer"); + + b.Property("MaximumSpecialCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumSpecialCharacterLevel") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequiresMasterClass") + .HasColumnType("boolean"); + + b.Property("SaveRankingStatistics") + .HasColumnType("boolean"); + + b.Property("TicketItemId") + .HasColumnType("uuid"); + + b.Property("TicketItemLevel") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("EntranceId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("TicketItemId"); + + b.ToTable("MiniGameDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameRankingEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("GameInstanceId") + .HasColumnType("uuid"); + + b.Property("MiniGameId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Score") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("MiniGameId"); + + b.ToTable("MiniGameRankingEntry", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ItemRewardId") + .HasColumnType("uuid"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("RequiredKillId") + .HasColumnType("uuid"); + + b.Property("RequiredSuccess") + .HasColumnType("integer"); + + b.Property("RewardAmount") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ItemRewardId"); + + b.HasIndex("MiniGameDefinitionId"); + + b.HasIndex("RequiredKillId"); + + b.ToTable("MiniGameReward", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameSpawnWave", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MiniGameDefinitionId") + .HasColumnType("uuid"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("WaveNumber") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameDefinitionId"); + + b.ToTable("MiniGameSpawnWave", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameTerrainChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EndX") + .HasColumnType("smallint"); + + b.Property("EndY") + .HasColumnType("smallint"); + + b.Property("IsClientUpdateRequired") + .HasColumnType("boolean"); + + b.Property("MiniGameChangeEventId") + .HasColumnType("uuid"); + + b.Property("SetTerrainAttribute") + .HasColumnType("boolean"); + + b.Property("StartX") + .HasColumnType("smallint"); + + b.Property("StartY") + .HasColumnType("smallint"); + + b.Property("TerrainAttribute") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("MiniGameChangeEventId"); + + b.ToTable("MiniGameTerrainChange", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeDefinitionId") + .HasColumnType("uuid"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("AttributeDefinitionId"); + + b.HasIndex("MonsterDefinitionId"); + + b.ToTable("MonsterAttribute", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttackDelay") + .HasColumnType("interval"); + + b.Property("AttackRange") + .HasColumnType("smallint"); + + b.Property("AttackSkillId") + .HasColumnType("uuid"); + + b.Property("Attribute") + .HasColumnType("smallint"); + + b.Property("Designation") + .IsRequired() + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IntelligenceTypeName") + .HasColumnType("text"); + + b.Property("MerchantStoreId") + .HasColumnType("uuid"); + + b.Property("MoveDelay") + .HasColumnType("interval"); + + b.Property("MoveRange") + .HasColumnType("smallint"); + + b.Property("NpcWindow") + .HasColumnType("integer"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("NumberOfMaximumItemDrops") + .HasColumnType("integer"); + + b.Property("ObjectKind") + .HasColumnType("integer"); + + b.Property("RespawnDelay") + .HasColumnType("interval"); + + b.Property("ViewRange") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AttackSkillId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MerchantStoreId") + .IsUnique(); + + b.ToTable("MonsterDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinitionDropItemGroup", b => + { + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.HasKey("MonsterDefinitionId", "DropItemGroupId"); + + b.HasIndex("DropItemGroupId"); + + b.ToTable("MonsterDefinitionDropItemGroup", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("GameMapId") + .HasColumnType("uuid"); + + b.Property("MaximumHealthOverride") + .HasColumnType("integer"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasColumnType("smallint"); + + b.Property("SpawnTrigger") + .HasColumnType("integer"); + + b.Property("WaveNumber") + .HasColumnType("smallint"); + + 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("GameMapId"); + + b.HasIndex("MonsterDefinitionId"); + + b.ToTable("MonsterSpawnArea", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PlugInConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomConfiguration") + .HasColumnType("text"); + + b.Property("CustomPlugInSource") + .HasColumnType("text"); + + b.Property("ExternalAssemblyName") + .HasColumnType("text"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("TypeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.ToTable("PlugInConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BoostId") + .HasColumnType("uuid"); + + b.Property("GameMapDefinitionId") + .HasColumnType("uuid"); + + b.Property("MagicEffectDefinitionId") + .HasColumnType("uuid"); + + b.Property("MagicEffectDefinitionId1") + .HasColumnType("uuid"); + + b.Property("TargetAttributeId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BoostId") + .IsUnique(); + + b.HasIndex("GameMapDefinitionId"); + + b.HasIndex("MagicEffectDefinitionId"); + + b.HasIndex("MagicEffectDefinitionId1"); + + b.HasIndex("TargetAttributeId"); + + b.ToTable("PowerUpDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AggregateType") + .HasColumnType("integer"); + + b.Property("MaximumValue") + .HasColumnType("real"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.ToTable("PowerUpDefinitionValue", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Group") + .HasColumnType("smallint"); + + b.Property("MaximumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MinimumCharacterLevel") + .HasColumnType("integer"); + + b.Property("MonsterDefinitionId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("QualifiedCharacterId") + .HasColumnType("uuid"); + + b.Property("QuestGiverId") + .HasColumnType("uuid"); + + b.Property("RefuseNumber") + .HasColumnType("smallint"); + + b.Property("Repeatable") + .HasColumnType("boolean"); + + b.Property("RequiredStartMoney") + .HasColumnType("integer"); + + b.Property("RequiresClientAction") + .HasColumnType("boolean"); + + b.Property("StartingNumber") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("MonsterDefinitionId"); + + b.HasIndex("QualifiedCharacterId"); + + b.HasIndex("QuestGiverId"); + + b.ToTable("QuestDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestItemRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DropItemGroupId") + .HasColumnType("uuid"); + + b.Property("ItemId") + .HasColumnType("uuid"); + + b.Property("MinimumNumber") + .HasColumnType("integer"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DropItemGroupId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuestDefinitionId"); + + b.ToTable("QuestItemRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MinimumNumber") + .HasColumnType("integer"); + + b.Property("MonsterId") + .HasColumnType("uuid"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonsterId"); + + b.HasIndex("QuestDefinitionId"); + + b.ToTable("QuestMonsterKillRequirement", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirementState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterQuestStateId") + .HasColumnType("uuid"); + + b.Property("KillCount") + .HasColumnType("integer"); + + b.Property("RequirementId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CharacterQuestStateId"); + + b.HasIndex("RequirementId"); + + b.ToTable("QuestMonsterKillRequirementState", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeRewardId") + .HasColumnType("uuid"); + + b.Property("ItemRewardId") + .HasColumnType("uuid"); + + b.Property("QuestDefinitionId") + .HasColumnType("uuid"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("SkillRewardId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AttributeRewardId"); + + b.HasIndex("ItemRewardId") + .IsUnique(); + + b.HasIndex("QuestDefinitionId"); + + b.HasIndex("SkillRewardId"); + + b.ToTable("QuestReward", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .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.ToTable("Rectangle", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumSuccessPercent") + .HasColumnType("smallint"); + + b.Property("Money") + .HasColumnType("integer"); + + b.Property("MoneyPerFinalSuccessPercentage") + .HasColumnType("integer"); + + b.Property("MultipleAllowed") + .HasColumnType("boolean"); + + b.Property("NpcPriceDivisor") + .HasColumnType("integer"); + + b.Property("ResultItemExcellentOptionChance") + .HasColumnType("smallint"); + + b.Property("ResultItemLuckOptionChance") + .HasColumnType("smallint"); + + b.Property("ResultItemMaxExcOptionCount") + .HasColumnType("smallint"); + + b.Property("ResultItemSelect") + .HasColumnType("integer"); + + b.Property("ResultItemSkillChance") + .HasColumnType("smallint"); + + b.Property("SuccessPercent") + .HasColumnType("smallint"); + + b.Property("SuccessPercentageAdditionForAncientItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForExcellentItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForGuardianItem") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForLuck") + .HasColumnType("integer"); + + b.Property("SuccessPercentageAdditionForSocketItem") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("SimpleCraftingSettings", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSkillSettingsId") + .HasColumnType("uuid"); + + b.Property("AttackDamage") + .HasColumnType("integer"); + + b.Property("DamageType") + .HasColumnType("integer"); + + b.Property("ElementalModifierTargetId") + .HasColumnType("uuid"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("ImplicitTargetRange") + .HasColumnType("smallint"); + + b.Property("MagicEffectDefId") + .HasColumnType("uuid"); + + b.Property("MasterDefinitionId") + .HasColumnType("uuid"); + + b.Property("MovesTarget") + .HasColumnType("boolean"); + + b.Property("MovesToTarget") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("smallint"); + + b.Property("NumberOfHitsPerAttack") + .HasColumnType("smallint"); + + b.Property("Range") + .HasColumnType("smallint"); + + b.Property("SkillType") + .HasColumnType("integer"); + + b.Property("SkipElementalModifier") + .HasColumnType("boolean"); + + b.Property("Target") + .HasColumnType("integer"); + + b.Property("TargetRestriction") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AreaSkillSettingsId") + .IsUnique(); + + b.HasIndex("ElementalModifierTargetId"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("MagicEffectDefId"); + + b.HasIndex("MasterDefinitionId") + .IsUnique(); + + b.ToTable("Skill", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillCharacterClass", b => + { + b.Property("SkillId") + .HasColumnType("uuid"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.HasKey("SkillId", "CharacterClassId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("SkillCharacterClass", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MaximumCompletionTime") + .HasColumnType("interval"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("SkillComboDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsFinalStep") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("SkillComboDefinitionId") + .HasColumnType("uuid"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SkillComboDefinitionId"); + + b.HasIndex("SkillId"); + + b.ToTable("SkillComboStep", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("SkillId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("SkillId"); + + b.ToTable("SkillEntry", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttribute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("DefinitionId") + .HasColumnType("uuid"); + + b.Property("Value") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CharacterId"); + + b.HasIndex("DefinitionId"); + + b.ToTable("StatAttribute", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttributeDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributeId") + .HasColumnType("uuid"); + + b.Property("BaseValue") + .HasColumnType("real"); + + b.Property("CharacterClassId") + .HasColumnType("uuid"); + + b.Property("IncreasableByPlayer") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AttributeId"); + + b.HasIndex("CharacterClassId"); + + b.ToTable("StatAttributeDefinition", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SystemConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AutoStart") + .HasColumnType("boolean"); + + b.Property("AutoUpdateSchema") + .HasColumnType("boolean"); + + b.Property("IpResolver") + .HasColumnType("integer"); + + b.Property("IpResolverParameter") + .HasColumnType("text"); + + b.Property("ReadConsoleInput") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("SystemConfiguration", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.WarpInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Costs") + .HasColumnType("integer"); + + b.Property("GameConfigurationId") + .HasColumnType("uuid"); + + b.Property("GateId") + .HasColumnType("uuid"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("LevelRequirement") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameConfigurationId"); + + b.HasIndex("GateId"); + + b.ToTable("WarpInfo", "config"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawVault") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", "VaultId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawVault"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AccountCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", "Account") + .WithMany("JoinedUnlockedCharacterClasses") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("CharacterClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawCharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId"); + + b.Navigation("RawCharacterClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawAttributes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRelationship", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", null) + .WithMany("RawAttributeCombinations") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawGlobalAttributeCombinations") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawInputAttribute") + .WithMany() + .HasForeignKey("InputAttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawOperandAttribute") + .WithMany() + .HasForeignKey("OperandAttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", null) + .WithMany("RawRelatedValues") + .HasForeignKey("PowerUpDefinitionValueId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawAttributeRelationships") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawInputAttribute"); + + b.Navigation("RawOperandAttribute"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttribute") + .WithMany() + .HasForeignKey("AttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawMapRequirements") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawRequirements") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawConsumeRequirements") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", null) + .WithMany("RawRequirements") + .HasForeignKey("SkillId1") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawGround") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "GroundId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawLeftGoal") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "LeftGoalId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Rectangle", "RawRightGoal") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "RightGoalId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawGround"); + + b.Navigation("RawLeftGoal"); + + b.Navigation("RawRightGoal"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawMagicEffectDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", "MagicEffectDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawBuffs") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMagicEffectDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawAttackRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "AttackRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCastleSiegeMapDefinition") + .WithMany() + .HasForeignKey("CastleSiegeMapDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawDefenseRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "DefenseRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawLandOfTrialsMapDefinition") + .WithMany() + .HasForeignKey("LandOfTrialsMapDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawRewardItemDefinition") + .WithMany() + .HasForeignKey("RewardItemDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("RawAttackRespawnArea"); + + b.Navigation("RawCastleSiegeMapDefinition"); + + b.Navigation("RawDefenseRespawnArea"); + + b.Navigation("RawLandOfTrialsMapDefinition"); + + b.Navigation("RawRewardItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany() + .HasForeignKey("OwnerGuildId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany() + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawNpcDefinitions") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition") + .WithMany() + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("RawMonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", null) + .WithMany("RawNpcStates") + .HasForeignKey("CastleSiegeDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStateSchedule") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId2") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId3") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueRegenUpgrades") + .HasForeignKey("CastleSiegeConfigurationId4") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawAttackMachineZones") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawDefenseMachineZones") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null) + .WithMany("RawCharacters") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawCharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCurrentMap") + .WithMany() + .HasForeignKey("CurrentMapId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawInventory") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "InventoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCharacterClass"); + + b.Navigation("RawCurrentMap"); + + b.Navigation("RawInventory"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", "RawComboDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "ComboDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawCharacterClasses") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawHomeMap") + .WithMany() + .HasForeignKey("HomeMapId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawNextGenerationClass") + .WithMany() + .HasForeignKey("NextGenerationClassId"); + + b.Navigation("RawComboDefinition"); + + b.Navigation("RawHomeMap"); + + b.Navigation("RawNextGenerationClass"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Character") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + + b.Navigation("DropItemGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", "RawActiveQuest") + .WithMany() + .HasForeignKey("ActiveQuestId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawQuestStates") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", "RawLastFinishedQuest") + .WithMany() + .HasForeignKey("LastFinishedQuestId"); + + b.Navigation("RawActiveQuest"); + + b.Navigation("RawLastFinishedQuest"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerEndpoint", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", null) + .WithMany("RawEndpoints") + .HasForeignKey("ChatServerDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CombinationBonusRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", null) + .WithMany("RawRequirements") + .HasForeignKey("ItemOptionCombinationBonusId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.Navigation("RawOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConnectServerDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConstValueAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany("RawBaseAttributeValues") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "GameConfiguration") + .WithMany("RawGlobalBaseAttributeValues") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("CharacterClass"); + + b.Navigation("GameConfiguration"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawDropItemGroups") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroupItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany("JoinedPossibleItems") + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelArea", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", null) + .WithMany("RawDuelAreas") + .HasForeignKey("DuelConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawFirstPlayerGate") + .WithMany() + .HasForeignKey("FirstPlayerGateId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawSecondPlayerGate") + .WithMany() + .HasForeignKey("SecondPlayerGateId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawSpectatorsGate") + .WithMany() + .HasForeignKey("SpectatorsGateId"); + + b.Navigation("RawFirstPlayerGate"); + + b.Navigation("RawSecondPlayerGate"); + + b.Navigation("RawSpectatorsGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawExit") + .WithMany() + .HasForeignKey("ExitId"); + + b.Navigation("RawExit"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.EnterGate", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawEnterGates") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawTargetGate") + .WithMany() + .HasForeignKey("TargetGateId"); + + b.Navigation("RawTargetGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawMap") + .WithMany("RawExitGates") + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMap"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "RawCastleSiegeConfiguration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", "RawDuelConfiguration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "DuelConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCastleSiegeConfiguration"); + + b.Navigation("RawDuelConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", "RawBattleZone") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "BattleZoneId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMaps") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawSafezoneMap") + .WithMany() + .HasForeignKey("SafezoneMapId"); + + b.Navigation("RawBattleZone"); + + b.Navigation("RawSafezoneMap"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinitionDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "GameMapDefinition") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("GameMapDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfigurationGameMapDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "GameMapDefinition") + .WithMany() + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", "GameServerConfiguration") + .WithMany("JoinedMaps") + .HasForeignKey("GameServerConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GameMapDefinition"); + + b.Navigation("GameServerConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "RawGameConfiguration") + .WithMany() + .HasForeignKey("GameConfigurationId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", "RawServerConfiguration") + .WithMany() + .HasForeignKey("ServerConfigurationId"); + + b.Navigation("RawGameConfiguration"); + + b.Navigation("RawServerConfiguration"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerEndpoint", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameClientDefinition", "RawClient") + .WithMany() + .HasForeignKey("ClientId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", null) + .WithMany("RawEndpoints") + .HasForeignKey("GameServerDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawClient"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", "RawAllianceGuild") + .WithMany() + .HasForeignKey("AllianceGuildId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", "RawHostility") + .WithMany() + .HasForeignKey("HostilityId"); + + b.Navigation("RawAllianceGuild"); + + b.Navigation("RawHostility"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GuildMember", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany("RawMembers") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Character") + .WithMany() + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", null) + .WithMany("RawPossibleOptions") + .HasForeignKey("ItemOptionDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawOptionType"); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawItemStorage") + .WithMany("RawItems") + .HasForeignKey("ItemStorageId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawDefinition"); + + b.Navigation("RawItemStorage"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", null) + .WithMany("RawEquippedItems") + .HasForeignKey("AppearanceDataId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearanceItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", "ItemAppearance") + .WithMany("JoinedVisibleOptions") + .HasForeignKey("ItemAppearanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "ItemOptionType") + .WithMany() + .HasForeignKey("ItemOptionTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemAppearance"); + + b.Navigation("ItemOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemBasePowerUpDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", "RawBonusPerLevelTable") + .WithMany() + .HasForeignKey("BonusPerLevelTableId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawBasePowerUpAttributes") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawBonusPerLevelTable"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawItemCraftings") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", "RawSimpleCraftingSettings") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCrafting", "SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawSimpleCraftingSettings"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", null) + .WithMany("RawRequiredItems") + .HasForeignKey("SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", "ItemCraftingRequiredItem") + .WithMany("JoinedPossibleItems") + .HasForeignKey("ItemCraftingRequiredItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemCraftingRequiredItem"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItemItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", "ItemCraftingRequiredItem") + .WithMany("JoinedRequiredItemOptions") + .HasForeignKey("ItemCraftingRequiredItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "ItemOptionType") + .WithMany() + .HasForeignKey("ItemOptionTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemCraftingRequiredItem"); + + b.Navigation("ItemOptionType"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingResultItem", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", null) + .WithMany("RawResultItems") + .HasForeignKey("SimpleCraftingSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawConsumeEffect") + .WithMany() + .HasForeignKey("ConsumeEffectId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItems") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", "RawItemSlot") + .WithMany() + .HasForeignKey("ItemSlotId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawConsumeEffect"); + + b.Navigation("RawItemSlot"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedQualifiedCharacters") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CharacterClass"); + + b.Navigation("ItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemOptionDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedPossibleItemOptions") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", "ItemOptionDefinition") + .WithMany() + .HasForeignKey("ItemOptionDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemOptionDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinitionItemSetGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany("JoinedPossibleItemSetGroups") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", "ItemSetGroup") + .WithMany() + .HasForeignKey("ItemSetGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemSetGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", null) + .WithMany("RawDropItems") + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroupItemDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "ItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", "ItemDropItemGroup") + .WithMany("JoinedPossibleItems") + .HasForeignKey("ItemDropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ItemDefinition"); + + b.Navigation("ItemDropItemGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemItemOfItemSet", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", "Item") + .WithMany("JoinedItemSetGroups") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", "ItemOfItemSet") + .WithMany() + .HasForeignKey("ItemOfItemSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ItemOfItemSet"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemLevelBonusTables") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOfItemSet", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "RawBonusOption") + .WithMany() + .HasForeignKey("BonusOptionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItemDefinition") + .WithMany() + .HasForeignKey("ItemDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", "RawItemSetGroup") + .WithMany("RawItems") + .HasForeignKey("ItemSetGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawBonusOption"); + + b.Navigation("RawItemDefinition"); + + b.Navigation("RawItemSetGroup"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", "RawOptionType") + .WithMany() + .HasForeignKey("OptionTypeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOption", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawOptionType"); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawBonus") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", "BonusId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptionCombinationBonuses") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawBonus"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptions") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionLink", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", null) + .WithMany("RawItemOptions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", "RawItemOption") + .WithMany() + .HasForeignKey("ItemOptionId"); + + b.Navigation("RawItemOption"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", null) + .WithMany("RawLevelDependentOptions") + .HasForeignKey("IncreasableItemOptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "RawPowerUpDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionOfLevel", "PowerUpDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawPowerUpDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemOptionTypes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemSetGroups") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", "RawOptions") + .WithMany() + .HasForeignKey("OptionsId"); + + b.Navigation("RawOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSlotType", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawItemSlotTypes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.JewelMix", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawJewelMixes") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawMixedJewel") + .WithMany() + .HasForeignKey("MixedJewelId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawSingleJewel") + .WithMany() + .HasForeignKey("SingleJewelId"); + + b.Navigation("RawMixedJewel"); + + b.Navigation("RawSingleJewel"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", "RawHeader") + .WithMany() + .HasForeignKey("HeaderId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", "RawSenderAppearance") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterBody", "SenderAppearanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawHeader"); + + b.Navigation("RawSenderAppearance"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LetterHeader", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "Receiver") + .WithMany("RawLetters") + .HasForeignKey("ReceiverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Receiver"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.LevelBonus", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", null) + .WithMany("RawBonusPerLevel") + .HasForeignKey("ItemLevelBonusTableId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawChance") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "ChanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawChancePvp") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "ChancePvpId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawDuration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "DurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawDurationPvp") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "DurationPvpId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMagicEffects") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawChance"); + + b.Navigation("RawChancePvp"); + + b.Navigation("RawDuration"); + + b.Navigation("RawDurationPvp"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawReplacedSkill") + .WithMany() + .HasForeignKey("ReplacedSkillId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", "RawRoot") + .WithMany() + .HasForeignKey("RootId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawReplacedSkill"); + + b.Navigation("RawRoot"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinitionSkill", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", "MasterSkillDefinition") + .WithMany("JoinedRequiredMasterSkills") + .HasForeignKey("MasterSkillDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MasterSkillDefinition"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillRoot", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMasterSkillRoots") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawChangeEvents") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", "RawSpawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", "SpawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawTargetDefinition") + .WithMany() + .HasForeignKey("TargetDefinitionId"); + + b.Navigation("RawSpawnArea"); + + b.Navigation("RawTargetDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawEntrance") + .WithMany() + .HasForeignKey("EntranceId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMiniGameDefinitions") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawTicketItem") + .WithMany() + .HasForeignKey("TicketItemId"); + + b.Navigation("RawEntrance"); + + b.Navigation("RawTicketItem"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameRankingEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", "RawCharacter") + .WithMany() + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", "RawMiniGame") + .WithMany() + .HasForeignKey("MiniGameId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawCharacter"); + + b.Navigation("RawMiniGame"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameReward", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "RawItemReward") + .WithMany() + .HasForeignKey("ItemRewardId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawRewards") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawRequiredKill") + .WithMany() + .HasForeignKey("RequiredKillId"); + + b.Navigation("RawItemReward"); + + b.Navigation("RawRequiredKill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameSpawnWave", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", null) + .WithMany("RawSpawnWaves") + .HasForeignKey("MiniGameDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameTerrainChange", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", null) + .WithMany("RawTerrainChanges") + .HasForeignKey("MiniGameChangeEventId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttributeDefinition") + .WithMany() + .HasForeignKey("AttributeDefinitionId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawAttributes") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttributeDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawAttackSkill") + .WithMany() + .HasForeignKey("AttackSkillId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawMonsters") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", "RawMerchantStore") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "MerchantStoreId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttackSkill"); + + b.Navigation("RawMerchantStore"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinitionDropItemGroup", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "DropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "MonsterDefinition") + .WithMany("JoinedDropItemGroups") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DropItemGroup"); + + b.Navigation("MonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterSpawnArea", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawGameMap") + .WithMany("RawMonsterSpawns") + .HasForeignKey("GameMapId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition") + .WithMany() + .HasForeignKey("MonsterDefinitionId"); + + b.Navigation("RawGameMap"); + + b.Navigation("RawMonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PlugInConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawPlugInConfigurations") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", "RawBoost") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinition", "BoostId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", null) + .WithMany("RawCharacterPowerUpDefinitions") + .HasForeignKey("GameMapDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", null) + .WithMany("RawPowerUpDefinitions") + .HasForeignKey("MagicEffectDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", null) + .WithMany("RawPowerUpDefinitionsPvp") + .HasForeignKey("MagicEffectDefinitionId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_PowerUpDefinition_MagicEffectDefinition_MagicEffectDefinit~1"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawTargetAttribute") + .WithMany() + .HasForeignKey("TargetAttributeId"); + + b.Navigation("RawBoost"); + + b.Navigation("RawTargetAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null) + .WithMany("RawQuests") + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "RawQualifiedCharacter") + .WithMany() + .HasForeignKey("QualifiedCharacterId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawQuestGiver") + .WithMany() + .HasForeignKey("QuestGiverId"); + + b.Navigation("RawQualifiedCharacter"); + + b.Navigation("RawQuestGiver"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestItemRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", "RawDropItemGroup") + .WithMany() + .HasForeignKey("DropItemGroupId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawItem") + .WithMany() + .HasForeignKey("ItemId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRequiredItems") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawDropItemGroup"); + + b.Navigation("RawItem"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonster") + .WithMany() + .HasForeignKey("MonsterId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRequiredMonsterKills") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawMonster"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirementState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", null) + .WithMany("RawRequirementStates") + .HasForeignKey("CharacterQuestStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestMonsterKillRequirement", "RawRequirement") + .WithMany() + .HasForeignKey("RequirementId"); + + b.Navigation("RawRequirement"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttributeReward") + .WithMany() + .HasForeignKey("AttributeRewardId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", "RawItemReward") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestReward", "ItemRewardId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", null) + .WithMany("RawRewards") + .HasForeignKey("QuestDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkillReward") + .WithMany() + .HasForeignKey("SkillRewardId"); + + b.Navigation("RawAttributeReward"); + + b.Navigation("RawItemReward"); + + b.Navigation("RawSkillReward"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AreaSkillSettings", "RawAreaSkillSettings") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "AreaSkillSettingsId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawElementalModifierTarget") + .WithMany() + .HasForeignKey("ElementalModifierTargetId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawSkills") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawMagicEffectDef") + .WithMany() + .HasForeignKey("MagicEffectDefId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", "RawMasterDefinition") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "MasterDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAreaSkillSettings"); + + b.Navigation("RawElementalModifierTarget"); + + b.Navigation("RawMagicEffectDef"); + + b.Navigation("RawMasterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillCharacterClass", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", "CharacterClass") + .WithMany() + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "Skill") + .WithMany("JoinedQualifiedCharacters") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CharacterClass"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboStep", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", null) + .WithMany("RawSteps") + .HasForeignKey("SkillComboDefinitionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawLearnedSkills") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", "RawSkill") + .WithMany() + .HasForeignKey("SkillId"); + + b.Navigation("RawSkill"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttribute", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null) + .WithMany("RawAttributes") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", null) + .WithMany("RawAttributes") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawDefinition") + .WithMany() + .HasForeignKey("DefinitionId"); + + b.Navigation("RawDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.StatAttributeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", "RawAttribute") + .WithMany() + .HasForeignKey("AttributeId"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", null) + .WithMany("RawStatAttributes") + .HasForeignKey("CharacterClassId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("RawAttribute"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.WarpInfo", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", null) + .WithMany("RawWarpList") + .HasForeignKey("GameConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ExitGate", "RawGate") + .WithMany() + .HasForeignKey("GateId"); + + b.Navigation("RawGate"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b => + { + b.Navigation("JoinedUnlockedCharacterClasses"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawCharacters"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b => + { + b.Navigation("RawEquippedItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.Navigation("RawAttackMachineZones"); + + b.Navigation("RawDefenseMachineZones"); + + b.Navigation("RawGateDefenseUpgrades"); + + b.Navigation("RawGateLifeUpgrades"); + + b.Navigation("RawNpcDefinitions"); + + b.Navigation("RawStateSchedule"); + + b.Navigation("RawStatueDefenseUpgrades"); + + b.Navigation("RawStatueLifeUpgrades"); + + b.Navigation("RawStatueRegenUpgrades"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b => + { + b.Navigation("RawNpcStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawLearnedSkills"); + + b.Navigation("RawLetters"); + + b.Navigation("RawQuestStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b => + { + b.Navigation("RawAttributeCombinations"); + + b.Navigation("RawBaseAttributeValues"); + + b.Navigation("RawStatAttributes"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b => + { + b.Navigation("RawRequirementStates"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", b => + { + b.Navigation("RawEndpoints"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b => + { + b.Navigation("JoinedPossibleItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b => + { + b.Navigation("RawDuelAreas"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => + { + b.Navigation("RawAttributes"); + + b.Navigation("RawCharacterClasses"); + + b.Navigation("RawDropItemGroups"); + + b.Navigation("RawGlobalAttributeCombinations"); + + b.Navigation("RawGlobalBaseAttributeValues"); + + b.Navigation("RawItemLevelBonusTables"); + + b.Navigation("RawItemOptionCombinationBonuses"); + + b.Navigation("RawItemOptionTypes"); + + b.Navigation("RawItemOptions"); + + b.Navigation("RawItemSetGroups"); + + b.Navigation("RawItemSlotTypes"); + + b.Navigation("RawItems"); + + b.Navigation("RawJewelMixes"); + + b.Navigation("RawMagicEffects"); + + b.Navigation("RawMaps"); + + b.Navigation("RawMasterSkillRoots"); + + b.Navigation("RawMiniGameDefinitions"); + + b.Navigation("RawMonsters"); + + b.Navigation("RawPlugInConfigurations"); + + b.Navigation("RawSkills"); + + b.Navigation("RawWarpList"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawCharacterPowerUpDefinitions"); + + b.Navigation("RawEnterGates"); + + b.Navigation("RawExitGates"); + + b.Navigation("RawMapRequirements"); + + b.Navigation("RawMonsterSpawns"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerConfiguration", b => + { + b.Navigation("JoinedMaps"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameServerDefinition", b => + { + b.Navigation("RawEndpoints"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", b => + { + b.Navigation("RawMembers"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.IncreasableItemOption", b => + { + b.Navigation("RawLevelDependentOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Item", b => + { + b.Navigation("JoinedItemSetGroups"); + + b.Navigation("RawItemOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemAppearance", b => + { + b.Navigation("JoinedVisibleOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemCraftingRequiredItem", b => + { + b.Navigation("JoinedPossibleItems"); + + b.Navigation("JoinedRequiredItemOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", b => + { + b.Navigation("JoinedPossibleItemOptions"); + + b.Navigation("JoinedPossibleItemSetGroups"); + + b.Navigation("JoinedQualifiedCharacters"); + + b.Navigation("RawBasePowerUpAttributes"); + + b.Navigation("RawDropItems"); + + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDropItemGroup", b => + { + b.Navigation("JoinedPossibleItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemLevelBonusTable", b => + { + b.Navigation("RawBonusPerLevel"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionCombinationBonus", b => + { + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemOptionDefinition", b => + { + b.Navigation("RawPossibleOptions"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemSetGroup", b => + { + b.Navigation("RawItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemStorage", b => + { + b.Navigation("RawItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", b => + { + b.Navigation("RawPowerUpDefinitions"); + + b.Navigation("RawPowerUpDefinitionsPvp"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MasterSkillDefinition", b => + { + b.Navigation("JoinedRequiredMasterSkills"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameChangeEvent", b => + { + b.Navigation("RawTerrainChanges"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MiniGameDefinition", b => + { + b.Navigation("RawChangeEvents"); + + b.Navigation("RawRewards"); + + b.Navigation("RawSpawnWaves"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", b => + { + b.Navigation("JoinedDropItemGroups"); + + b.Navigation("RawAttributes"); + + b.Navigation("RawBuffs"); + + b.Navigation("RawItemCraftings"); + + b.Navigation("RawQuests"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.PowerUpDefinitionValue", b => + { + b.Navigation("RawRelatedValues"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.QuestDefinition", b => + { + b.Navigation("RawRequiredItems"); + + b.Navigation("RawRequiredMonsterKills"); + + b.Navigation("RawRewards"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SimpleCraftingSettings", b => + { + b.Navigation("RawRequiredItems"); + + b.Navigation("RawResultItems"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Skill", b => + { + b.Navigation("JoinedQualifiedCharacters"); + + b.Navigation("RawAttributeRelationships"); + + b.Navigation("RawConsumeRequirements"); + + b.Navigation("RawRequirements"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.SkillComboDefinition", b => + { + b.Navigation("RawSteps"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.cs b/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.cs new file mode 100644 index 0000000..f612103 --- /dev/null +++ b/src/Persistence/EntityFramework/Migrations/20260801162427_ConfigureCastleSiegePersistence.cs @@ -0,0 +1,343 @@ +// +// 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 ConfigureCastleSiegePersistence : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~", + schema: "config", + table: "CastleSiegeNpcDefinition"); + + migrationBuilder.DropIndex( + name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId", + schema: "config", + table: "CastleSiegeNpcDefinition"); + + migrationBuilder.Sql( + """ + DO $$ + BEGIN + IF EXISTS (SELECT 1 FROM config."CastleSiegeNpcDefinition" WHERE "MonsterDefinitionId" IS NULL) THEN + RAISE EXCEPTION 'CastleSiegeNpcDefinition contains rows without a MonsterDefinitionId. Repair or remove these rows before applying this migration.'; + END IF; + END + $$; + """); + + migrationBuilder.AlterColumn( + name: "MonsterDefinitionId", + schema: "config", + table: "CastleSiegeNpcDefinition", + type: "uuid", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RegisterMinMembers", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + defaultValue: 20, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "RegisterMinLevel", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + defaultValue: 200, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "MaxAttackingGuilds", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + defaultValue: 3, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "CrownHoldTimeSeconds", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + defaultValue: 30, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.CreateTable( + name: "CastleSiegeGuildRegistration", + schema: "data", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + GuildId = table.Column(type: "uuid", nullable: false), + GuildName = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + Marks = table.Column(type: "integer", nullable: false), + RegistrationOrder = table.Column(type: "integer", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_CastleSiegeGuildRegistration", x => x.Id); + table.ForeignKey( + name: "FK_CastleSiegeGuildRegistration_Guild_GuildId", + column: x => x.GuildId, + principalSchema: "guild", + principalTable: "Guild", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcState_MonsterNumber_InstanceId", + schema: "data", + table: "CastleSiegeNpcState", + columns: new[] { "MonsterNumber", "InstanceId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId_InstanceId", + schema: "config", + table: "CastleSiegeNpcDefinition", + columns: new[] { "MonsterDefinitionId", "InstanceId" }); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeData_OwnerGuildId", + schema: "data", + table: "CastleSiegeData", + column: "OwnerGuildId"); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeGuildRegistration_GuildId", + schema: "data", + table: "CastleSiegeGuildRegistration", + column: "GuildId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "CastleSiegeMapDefinitionId", + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "LandOfTrialsMapDefinitionId", + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "RewardItemDefinitionId", + principalSchema: "config", + principalTable: "ItemDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeData_Guild_OwnerGuildId", + schema: "data", + table: "CastleSiegeData", + column: "OwnerGuildId", + principalSchema: "guild", + principalTable: "Guild", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~", + schema: "config", + table: "CastleSiegeNpcDefinition", + column: "MonsterDefinitionId", + principalSchema: "config", + principalTable: "MonsterDefinition", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~", + schema: "config", + table: "CastleSiegeConfiguration"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeData_Guild_OwnerGuildId", + schema: "data", + table: "CastleSiegeData"); + + migrationBuilder.DropForeignKey( + name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~", + schema: "config", + table: "CastleSiegeNpcDefinition"); + + migrationBuilder.DropTable( + name: "CastleSiegeGuildRegistration", + schema: "data"); + + migrationBuilder.DropIndex( + name: "IX_CastleSiegeNpcState_MonsterNumber_InstanceId", + schema: "data", + table: "CastleSiegeNpcState"); + + migrationBuilder.DropIndex( + name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId_InstanceId", + schema: "config", + table: "CastleSiegeNpcDefinition"); + + migrationBuilder.DropIndex( + name: "IX_CastleSiegeData_OwnerGuildId", + schema: "data", + table: "CastleSiegeData"); + + migrationBuilder.AlterColumn( + name: "MonsterDefinitionId", + schema: "config", + table: "CastleSiegeNpcDefinition", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "RegisterMinMembers", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer", + oldDefaultValue: 20); + + migrationBuilder.AlterColumn( + name: "RegisterMinLevel", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer", + oldDefaultValue: 200); + + migrationBuilder.AlterColumn( + name: "MaxAttackingGuilds", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer", + oldDefaultValue: 3); + + migrationBuilder.AlterColumn( + name: "CrownHoldTimeSeconds", + schema: "config", + table: "CastleSiegeConfiguration", + type: "integer", + nullable: false, + oldClrType: typeof(int), + oldType: "integer", + oldDefaultValue: 30); + + migrationBuilder.CreateIndex( + name: "IX_CastleSiegeNpcDefinition_MonsterDefinitionId", + schema: "config", + table: "CastleSiegeNpcDefinition", + column: "MonsterDefinitionId"); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_CastleSiegeMapDe~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "CastleSiegeMapDefinitionId", + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_GameMapDefinition_LandOfTrialsMapD~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "LandOfTrialsMapDefinitionId", + principalSchema: "config", + principalTable: "GameMapDefinition", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeConfiguration_ItemDefinition_RewardItemDefinitio~", + schema: "config", + table: "CastleSiegeConfiguration", + column: "RewardItemDefinitionId", + principalSchema: "config", + principalTable: "ItemDefinition", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_CastleSiegeNpcDefinition_MonsterDefinition_MonsterDefinitio~", + schema: "config", + table: "CastleSiegeNpcDefinition", + column: "MonsterDefinitionId", + principalSchema: "config", + principalTable: "MonsterDefinition", + principalColumn: "Id"); + } + } +} diff --git a/src/Persistence/EntityFramework/Migrations/EntityDataContextModelSnapshot.cs b/src/Persistence/EntityFramework/Migrations/EntityDataContextModelSnapshot.cs index 1488815..b601d7b 100644 --- a/src/Persistence/EntityFramework/Migrations/EntityDataContextModelSnapshot.cs +++ b/src/Persistence/EntityFramework/Migrations/EntityDataContextModelSnapshot.cs @@ -378,6 +378,329 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations 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") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30); + + 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") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(3); + + b.Property("ParticipantRewardMinSeconds") + .HasColumnType("integer"); + + b.Property("RegisterMinLevel") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(200); + + b.Property("RegisterMinMembers") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(20); + + 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.HasIndex("OwnerGuildId"); + + b.ToTable("CastleSiegeData", "data"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("GuildName") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Marks") + .HasColumnType("integer"); + + b.Property("RegistrationOrder") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.ToTable("CastleSiegeGuildRegistration", "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", "InstanceId"); + + 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.HasIndex("MonsterNumber", "InstanceId") + .IsUnique(); + + 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") @@ -1054,6 +1377,9 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations b.Property("AreaSkillHitsPlayer") .HasColumnType("boolean"); + b.Property("CastleSiegeConfigurationId") + .HasColumnType("uuid"); + b.Property("CharacterNameRegex") .HasColumnType("text"); @@ -1145,6 +1471,9 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations b.HasKey("Id"); + b.HasIndex("CastleSiegeConfigurationId") + .IsUnique(); + b.HasIndex("DuelConfigurationId") .IsUnique(); @@ -3638,6 +3967,139 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations b.Navigation("RawMagicEffectDefinition"); }); + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawAttackRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "AttackRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawCastleSiegeMapDefinition") + .WithMany() + .HasForeignKey("CastleSiegeMapDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", "RawDefenseRespawnArea") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "DefenseRespawnAreaId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.GameMapDefinition", "RawLandOfTrialsMapDefinition") + .WithMany() + .HasForeignKey("LandOfTrialsMapDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.ItemDefinition", "RawRewardItemDefinition") + .WithMany() + .HasForeignKey("RewardItemDefinitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("RawAttackRespawnArea"); + + b.Navigation("RawCastleSiegeMapDefinition"); + + b.Navigation("RawDefenseRespawnArea"); + + b.Navigation("RawLandOfTrialsMapDefinition"); + + b.Navigation("RawRewardItemDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany() + .HasForeignKey("OwnerGuildId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeGuildRegistration", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Guild", null) + .WithMany() + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawNpcDefinitions") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", "RawMonsterDefinition") + .WithMany() + .HasForeignKey("MonsterDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("RawMonsterDefinition"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeNpcState", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", null) + .WithMany("RawNpcStates") + .HasForeignKey("CastleSiegeDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeStateScheduleEntry", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStateSchedule") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeUpgradeDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawGateLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~1"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueDefenseUpgrades") + .HasForeignKey("CastleSiegeConfigurationId2") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~2"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueLifeUpgrades") + .HasForeignKey("CastleSiegeConfigurationId3") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~3"); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawStatueRegenUpgrades") + .HasForeignKey("CastleSiegeConfigurationId4") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeUpgradeDefinition_CastleSiegeConfiguration_Cast~4"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeZoneDefinition", b => + { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawAttackMachineZones") + .HasForeignKey("CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", null) + .WithMany("RawDefenseMachineZones") + .HasForeignKey("CastleSiegeConfigurationId1") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_CastleSiegeZoneDefinition_CastleSiegeConfiguration_CastleS~1"); + }); + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => { b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null) @@ -3887,11 +4349,18 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", b => { + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", "RawCastleSiegeConfiguration") + .WithOne() + .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "CastleSiegeConfigurationId") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", "RawDuelConfiguration") .WithOne() .HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.GameConfiguration", "DuelConfigurationId") .OnDelete(DeleteBehavior.Cascade); + b.Navigation("RawCastleSiegeConfiguration"); + b.Navigation("RawDuelConfiguration"); }); @@ -5023,6 +5492,32 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations b.Navigation("RawEquippedItems"); }); + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeConfiguration", b => + { + b.Navigation("RawAttackMachineZones"); + + b.Navigation("RawDefenseMachineZones"); + + b.Navigation("RawGateDefenseUpgrades"); + + b.Navigation("RawGateLifeUpgrades"); + + b.Navigation("RawNpcDefinitions"); + + b.Navigation("RawStateSchedule"); + + b.Navigation("RawStatueDefenseUpgrades"); + + b.Navigation("RawStatueLifeUpgrades"); + + b.Navigation("RawStatueRegenUpgrades"); + }); + + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CastleSiegeData", b => + { + b.Navigation("RawNpcStates"); + }); + modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b => { b.Navigation("JoinedDropItemGroups"); diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeConfiguration.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeConfiguration.Generated.cs new file mode 100644 index 0000000..caa77f9 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeConfiguration.Generated.cs @@ -0,0 +1,276 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeConfiguration), Schema = SchemaNames.Configuration)] +internal partial class CastleSiegeConfiguration : MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration, IIdentifiable +{ + + + /// + /// Gets or sets the identifier of this instance. + /// + public Guid Id { get; set; } + + /// + /// Gets the raw collection of . + /// + public ICollection RawStateSchedule { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection StateSchedule => base.StateSchedule ??= new CollectionAdapter(this.RawStateSchedule); + + /// + /// Gets the raw collection of . + /// + public ICollection RawNpcDefinitions { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection NpcDefinitions => base.NpcDefinitions ??= new CollectionAdapter(this.RawNpcDefinitions); + + /// + /// Gets the raw collection of . + /// + public ICollection RawGateDefenseUpgrades { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection GateDefenseUpgrades => base.GateDefenseUpgrades ??= new CollectionAdapter(this.RawGateDefenseUpgrades); + + /// + /// Gets the raw collection of . + /// + public ICollection RawGateLifeUpgrades { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection GateLifeUpgrades => base.GateLifeUpgrades ??= new CollectionAdapter(this.RawGateLifeUpgrades); + + /// + /// Gets the raw collection of . + /// + public ICollection RawStatueDefenseUpgrades { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection StatueDefenseUpgrades => base.StatueDefenseUpgrades ??= new CollectionAdapter(this.RawStatueDefenseUpgrades); + + /// + /// Gets the raw collection of . + /// + public ICollection RawStatueLifeUpgrades { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection StatueLifeUpgrades => base.StatueLifeUpgrades ??= new CollectionAdapter(this.RawStatueLifeUpgrades); + + /// + /// Gets the raw collection of . + /// + public ICollection RawStatueRegenUpgrades { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection StatueRegenUpgrades => base.StatueRegenUpgrades ??= new CollectionAdapter(this.RawStatueRegenUpgrades); + + /// + /// Gets the raw collection of . + /// + public ICollection RawAttackMachineZones { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection AttackMachineZones => base.AttackMachineZones ??= new CollectionAdapter(this.RawAttackMachineZones); + + /// + /// Gets the raw collection of . + /// + public ICollection RawDefenseMachineZones { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection DefenseMachineZones => base.DefenseMachineZones ??= new CollectionAdapter(this.RawDefenseMachineZones); + + /// + /// Gets or sets the identifier of . + /// + public Guid? CastleSiegeMapDefinitionId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(CastleSiegeMapDefinitionId))] + public GameMapDefinition RawCastleSiegeMapDefinition + { + get => base.CastleSiegeMapDefinition as GameMapDefinition; + set => base.CastleSiegeMapDefinition = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition CastleSiegeMapDefinition + { + get => base.CastleSiegeMapDefinition;set + { + base.CastleSiegeMapDefinition = value; + this.CastleSiegeMapDefinitionId = this.RawCastleSiegeMapDefinition?.Id; + } + } + + /// + /// Gets or sets the identifier of . + /// + public Guid? LandOfTrialsMapDefinitionId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(LandOfTrialsMapDefinitionId))] + public GameMapDefinition RawLandOfTrialsMapDefinition + { + get => base.LandOfTrialsMapDefinition as GameMapDefinition; + set => base.LandOfTrialsMapDefinition = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.GameMapDefinition LandOfTrialsMapDefinition + { + get => base.LandOfTrialsMapDefinition;set + { + base.LandOfTrialsMapDefinition = value; + this.LandOfTrialsMapDefinitionId = this.RawLandOfTrialsMapDefinition?.Id; + } + } + + /// + /// Gets or sets the identifier of . + /// + public Guid? RewardItemDefinitionId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(RewardItemDefinitionId))] + public ItemDefinition RawRewardItemDefinition + { + get => base.RewardItemDefinition as ItemDefinition; + set => base.RewardItemDefinition = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition RewardItemDefinition + { + get => base.RewardItemDefinition;set + { + base.RewardItemDefinition = value; + this.RewardItemDefinitionId = this.RawRewardItemDefinition?.Id; + } + } + + /// + /// Gets or sets the identifier of . + /// + public Guid? DefenseRespawnAreaId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(DefenseRespawnAreaId))] + public CastleSiegeZoneDefinition RawDefenseRespawnArea + { + get => base.DefenseRespawnArea as CastleSiegeZoneDefinition; + set => base.DefenseRespawnArea = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition DefenseRespawnArea + { + get => base.DefenseRespawnArea;set + { + base.DefenseRespawnArea = value; + this.DefenseRespawnAreaId = this.RawDefenseRespawnArea?.Id; + } + } + + /// + /// Gets or sets the identifier of . + /// + public Guid? AttackRespawnAreaId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(AttackRespawnAreaId))] + public CastleSiegeZoneDefinition RawAttackRespawnArea + { + get => base.AttackRespawnArea as CastleSiegeZoneDefinition; + set => base.AttackRespawnArea = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition AttackRespawnArea + { + get => base.AttackRespawnArea;set + { + base.AttackRespawnArea = value; + this.AttackRespawnAreaId = this.RawAttackRespawnArea?.Id; + } + } + + /// + 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeData.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeData.Generated.cs new file mode 100644 index 0000000..f2cbc1f --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeData.Generated.cs @@ -0,0 +1,56 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeData), Schema = SchemaNames.AccountData)] +internal partial class CastleSiegeData : MUnique.OpenMU.DataModel.Entities.CastleSiegeData, IIdentifiable +{ + + + + /// + /// Gets the raw collection of . + /// + public ICollection RawNpcStates { get; } = new EntityFramework.List(); + + /// + [NotMapped] + public override ICollection NpcStates => base.NpcStates ??= new CollectionAdapter(this.RawNpcStates); + + + /// + 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeGuildRegistration.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeGuildRegistration.Generated.cs new file mode 100644 index 0000000..b3ef532 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeGuildRegistration.Generated.cs @@ -0,0 +1,47 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeGuildRegistration), Schema = SchemaNames.AccountData)] +internal partial class CastleSiegeGuildRegistration : MUnique.OpenMU.DataModel.Entities.CastleSiegeGuildRegistration, IIdentifiable +{ + + + + + /// + 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeNpcDefinition.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeNpcDefinition.Generated.cs new file mode 100644 index 0000000..5d0aeb0 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeNpcDefinition.Generated.cs @@ -0,0 +1,91 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeNpcDefinition), Schema = SchemaNames.Configuration)] +internal partial class CastleSiegeNpcDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeNpcDefinition, IIdentifiable +{ + + + /// + /// Gets or sets the identifier of this instance. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the identifier of . + /// + public Guid? MonsterDefinitionId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(MonsterDefinitionId))] + public MonsterDefinition RawMonsterDefinition + { + get => base.MonsterDefinition as MonsterDefinition; + set => base.MonsterDefinition = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.MonsterDefinition MonsterDefinition + { + get => base.MonsterDefinition;set + { + base.MonsterDefinition = value; + this.MonsterDefinitionId = this.RawMonsterDefinition?.Id; + } + } + + /// + 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeNpcState.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeNpcState.Generated.cs new file mode 100644 index 0000000..8ffdca3 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeNpcState.Generated.cs @@ -0,0 +1,47 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeNpcState), Schema = SchemaNames.AccountData)] +internal partial class CastleSiegeNpcState : MUnique.OpenMU.DataModel.Entities.CastleSiegeNpcState, IIdentifiable +{ + + + + + /// + 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeStateScheduleEntry.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeStateScheduleEntry.Generated.cs new file mode 100644 index 0000000..46feb20 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeStateScheduleEntry.Generated.cs @@ -0,0 +1,65 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeStateScheduleEntry), Schema = SchemaNames.Configuration)] +internal partial class CastleSiegeStateScheduleEntry : MUnique.OpenMU.DataModel.Configuration.CastleSiegeStateScheduleEntry, IIdentifiable +{ + + + /// + /// 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeUpgradeDefinition.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeUpgradeDefinition.Generated.cs new file mode 100644 index 0000000..6e58f42 --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeUpgradeDefinition.Generated.cs @@ -0,0 +1,65 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeUpgradeDefinition), Schema = SchemaNames.Configuration)] +internal partial class CastleSiegeUpgradeDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeUpgradeDefinition, IIdentifiable +{ + + + /// + /// 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(); + } + + +} diff --git a/src/Persistence/EntityFramework/Model/CastleSiegeZoneDefinition.Generated.cs b/src/Persistence/EntityFramework/Model/CastleSiegeZoneDefinition.Generated.cs new file mode 100644 index 0000000..0d0fe7d --- /dev/null +++ b/src/Persistence/EntityFramework/Model/CastleSiegeZoneDefinition.Generated.cs @@ -0,0 +1,65 @@ +// +// 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.EntityFramework.Model; + +using System.ComponentModel.DataAnnotations.Schema; +using MUnique.OpenMU.Persistence; + +/// +/// The Entity Framework Core implementation of . +/// +[Table(nameof(CastleSiegeZoneDefinition), Schema = SchemaNames.Configuration)] +internal partial class CastleSiegeZoneDefinition : MUnique.OpenMU.DataModel.Configuration.CastleSiegeZoneDefinition, IIdentifiable +{ + + + /// + /// 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(); + } + + +} diff --git a/src/Persistence/Initialization/Updates/AddCastleSiegeDataUpdatePlugIn.cs b/src/Persistence/Initialization/Updates/AddCastleSiegeDataUpdatePlugIn.cs new file mode 100644 index 0000000..a153c37 --- /dev/null +++ b/src/Persistence/Initialization/Updates/AddCastleSiegeDataUpdatePlugIn.cs @@ -0,0 +1,59 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.DataModel.Entities; +using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Events; +using MUnique.OpenMU.PlugIns; + +/// +/// Adds the Castle Siege configuration and persistent state to an existing Season 6 database. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("CD201E33-37C9-4C85-95CC-16042B28E974")] +public class AddCastleSiegeDataUpdatePlugIn : UpdatePlugInBase +{ + /// + /// The plug-in name. + /// + internal const string PlugInName = "Add Castle Siege data"; + + /// + /// The plug-in description. + /// + internal const string PlugInDescription = "This update adds the Castle Siege configuration and persistent state."; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override UpdateVersion Version => UpdateVersion.AddCastleSiegeData; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + public override bool IsMandatory => true; + + /// + public override DateTime CreatedAt => new(2026, 07, 28, 20, 0, 0, DateTimeKind.Utc); + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + var initializer = new CastleSiegeInitializer(context, gameConfiguration); + var configuration = initializer.InitializeConfiguration(); + if (!(await context.GetAsync().ConfigureAwait(false)).Any()) + { + initializer.InitializeData(configuration); + } + } +} diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs index 09aea6f..83d2c5c 100644 --- a/src/Persistence/Initialization/Updates/UpdateVersion.cs +++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs @@ -529,4 +529,14 @@ public enum UpdateVersion /// The version of the . /// RepairImportedMapWarpsSeason6 = 104, + + /// + /// The version of the . + /// + /// + /// Upstream numbers this update 100. AdaMu already uses 95-104 for its own updates, so it is + /// renumbered to 105 here. Never reuse a number that has already shipped: the applied-update + /// bookkeeping is keyed on this value, so a collision would skip or re-run updates on live databases. + /// + AddCastleSiegeData = 105, } diff --git a/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs new file mode 100644 index 0000000..00e024a --- /dev/null +++ b/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs @@ -0,0 +1,228 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Events; + +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.DataModel.Entities; +using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; + +/// +/// Initializes the Castle Siege configuration and persistent state. +/// +internal sealed class CastleSiegeInitializer : InitializerBase +{ + private const short GateMonsterNumber = 277; + private const short StatueMonsterNumber = 283; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The game configuration. + public CastleSiegeInitializer(IContext context, GameConfiguration gameConfiguration) + : base(context, gameConfiguration) + { + } + + /// + public override void Initialize() + { + var configuration = this.InitializeConfiguration(); + this.InitializeData(configuration); + } + + /// + /// Initializes the Castle Siege configuration, if it does not exist yet. + /// + /// The Castle Siege configuration. + internal CastleSiegeConfiguration InitializeConfiguration() + { + if (this.GameConfiguration.CastleSiegeConfiguration is { } existingConfiguration) + { + return existingConfiguration; + } + + var configuration = this.Context.CreateNew(); + configuration.Enabled = true; + configuration.CrownHoldTimeSeconds = 30; + configuration.RegisterMinLevel = 200; + configuration.RegisterMinMembers = 20; + configuration.ParticipantRewardMinSeconds = 60; + configuration.MaxAttackingGuilds = 3; + configuration.GuildScoreCastleSiege = 0; + configuration.GuildScoreCastleSiegeMembers = 0; + configuration.GateBuyPrice = 9_500_000; + configuration.StatueBuyPrice = 4_500_000; + configuration.CastleSiegeMapDefinition = this.GameConfiguration.Maps.Single(map => map.Number == ValleyOfLoren.Number); + configuration.LandOfTrialsMapDefinition = this.GameConfiguration.Maps.Single(map => map.Number == LandOfTrials.Number); + + // AdaMu deliberately leaves StateSchedule empty. Upstream drives the cycle from a fixed weekly + // schedule; AdaMu drives it from CastleSiegeEventPlugIn (manual /cs commands, AdminPanel durations + // and the optional auto-open times in the plugin configuration). Nothing reads StateSchedule here. + this.InitializeNpcDefinitions(configuration); + this.InitializeUpgradeDefinitions(configuration); + this.InitializeMachineZones(configuration); + configuration.DefenseRespawnArea = this.CreateZone(74, 144, 115, 154); + configuration.AttackRespawnArea = this.CreateZone(35, 11, 144, 48); + + this.GameConfiguration.CastleSiegeConfiguration = configuration; + return configuration; + } + + /// + /// Initializes the persistent Castle Siege state. + /// + /// The Castle Siege configuration. + /// The persistent Castle Siege state. + internal CastleSiegeData InitializeData(CastleSiegeConfiguration configuration) + { + var data = this.Context.CreateNew(); + data.OwnerGuildId = null; + data.IsOccupied = false; + data.TaxChaos = 0; + data.TaxStore = 0; + data.TaxHunt = 0; + data.IsHuntZoneEnabled = false; + data.TributeMoney = 0; + + var gateHitPoints = configuration.GateLifeUpgrades.Single(upgrade => upgrade.Level == 0).Value; + var statueHitPoints = configuration.StatueLifeUpgrades.Single(upgrade => upgrade.Level == 0).Value; + foreach (var npcDefinition in configuration.NpcDefinitions.Where(definition => definition.IsPersistedToDatabase)) + { + var npcState = this.Context.CreateNew(); + npcState.MonsterNumber = npcDefinition.MonsterDefinition!.Number; + npcState.InstanceId = npcDefinition.InstanceId; + npcState.DefenseLevel = 0; + npcState.RegenLevel = 0; + npcState.LifeLevel = 0; + npcState.CurrentHp = npcState.MonsterNumber switch + { + GateMonsterNumber => gateHitPoints, + StatueMonsterNumber => statueHitPoints, + _ => throw new InvalidOperationException($"The persisted Castle Siege NPC monster number {npcState.MonsterNumber} is unsupported."), + }; + data.NpcStates.Add(npcState); + } + + return data; + } + + private void InitializeNpcDefinitions(CastleSiegeConfiguration configuration) + { + this.AddNpc(configuration, 216, 1, false, CastleSiegeJoinSide.Attack1, 176, 212, Direction.SouthWest); + this.AddNpc(configuration, 217, 1, false, CastleSiegeJoinSide.Attack1, 167, 194, Direction.NorthWest); + this.AddNpc(configuration, 218, 1, false, CastleSiegeJoinSide.Attack1, 184, 195, Direction.NorthWest); + + this.AddNpc(configuration, 219, 1, false, CastleSiegeJoinSide.Defense, 93, 208, Direction.SouthWest); + this.AddNpc(configuration, 219, 2, false, CastleSiegeJoinSide.Defense, 81, 165, Direction.SouthWest); + this.AddNpc(configuration, 219, 3, false, CastleSiegeJoinSide.Defense, 107, 165, Direction.SouthWest); + this.AddNpc(configuration, 219, 4, false, CastleSiegeJoinSide.Defense, 67, 118, Direction.SouthWest); + this.AddNpc(configuration, 219, 5, false, CastleSiegeJoinSide.Defense, 93, 118, Direction.SouthWest); + this.AddNpc(configuration, 219, 6, false, CastleSiegeJoinSide.Defense, 119, 118, Direction.SouthWest); + + this.AddNpc(configuration, 221, 1, false, CastleSiegeJoinSide.Attack1, 63, 19, Direction.NorthEast); + this.AddNpc(configuration, 221, 2, false, CastleSiegeJoinSide.Attack1, 119, 19, Direction.NorthEast); + this.AddNpc(configuration, 222, 1, false, CastleSiegeJoinSide.Defense, 80, 188, Direction.SouthWest); + this.AddNpc(configuration, 222, 2, false, CastleSiegeJoinSide.Defense, 105, 188, Direction.SouthWest); + + this.AddNpc(configuration, GateMonsterNumber, 1, true, CastleSiegeJoinSide.Defense, 93, 204, Direction.SouthWest); + this.AddNpc(configuration, GateMonsterNumber, 2, true, CastleSiegeJoinSide.Defense, 81, 161, Direction.SouthWest); + this.AddNpc(configuration, GateMonsterNumber, 3, true, CastleSiegeJoinSide.Defense, 107, 161, Direction.SouthWest); + this.AddNpc(configuration, GateMonsterNumber, 4, true, CastleSiegeJoinSide.Defense, 67, 114, Direction.SouthWest); + this.AddNpc(configuration, GateMonsterNumber, 5, true, CastleSiegeJoinSide.Defense, 93, 114, Direction.SouthWest); + this.AddNpc(configuration, GateMonsterNumber, 6, true, CastleSiegeJoinSide.Defense, 119, 114, Direction.SouthWest); + + this.AddNpc(configuration, StatueMonsterNumber, 1, true, CastleSiegeJoinSide.Defense, 94, 227, Direction.SouthWest); + this.AddNpc(configuration, StatueMonsterNumber, 2, true, CastleSiegeJoinSide.Defense, 94, 182, Direction.SouthWest); + this.AddNpc(configuration, StatueMonsterNumber, 3, true, CastleSiegeJoinSide.Defense, 82, 130, Direction.SouthWest); + this.AddNpc(configuration, StatueMonsterNumber, 4, true, CastleSiegeJoinSide.Defense, 107, 130, Direction.SouthWest); + } + + private void InitializeUpgradeDefinitions(CastleSiegeConfiguration configuration) + { + this.AddUpgrade(configuration.GateDefenseUpgrades, 0, 0, 0, 100); + this.AddUpgrade(configuration.GateDefenseUpgrades, 1, 2, 3_000_000, 180); + this.AddUpgrade(configuration.GateDefenseUpgrades, 2, 3, 3_000_000, 300); + this.AddUpgrade(configuration.GateDefenseUpgrades, 3, 4, 3_000_000, 520); + + this.AddUpgrade(configuration.StatueDefenseUpgrades, 0, 0, 0, 80); + this.AddUpgrade(configuration.StatueDefenseUpgrades, 1, 3, 3_000_000, 180); + this.AddUpgrade(configuration.StatueDefenseUpgrades, 2, 5, 3_000_000, 340); + this.AddUpgrade(configuration.StatueDefenseUpgrades, 3, 7, 3_000_000, 550); + + this.AddUpgrade(configuration.GateLifeUpgrades, 0, 0, 0, 1_900_000); + this.AddUpgrade(configuration.GateLifeUpgrades, 1, 2, 1_000_000, 2_500_000); + this.AddUpgrade(configuration.GateLifeUpgrades, 2, 3, 1_000_000, 3_500_000); + this.AddUpgrade(configuration.GateLifeUpgrades, 3, 4, 1_000_000, 5_200_000); + + this.AddUpgrade(configuration.StatueLifeUpgrades, 0, 0, 0, 1_500_000); + this.AddUpgrade(configuration.StatueLifeUpgrades, 1, 3, 1_000_000, 2_200_000); + this.AddUpgrade(configuration.StatueLifeUpgrades, 2, 5, 1_000_000, 3_400_000); + this.AddUpgrade(configuration.StatueLifeUpgrades, 3, 7, 1_000_000, 5_000_000); + + this.AddUpgrade(configuration.StatueRegenUpgrades, 0, 0, 0, 0); + this.AddUpgrade(configuration.StatueRegenUpgrades, 1, 3, 5_000_000, 1); + this.AddUpgrade(configuration.StatueRegenUpgrades, 2, 5, 5_000_000, 2); + this.AddUpgrade(configuration.StatueRegenUpgrades, 3, 7, 5_000_000, 3); + } + + private void InitializeMachineZones(CastleSiegeConfiguration configuration) + { + configuration.AttackMachineZones.Add(this.CreateZone(62, 103, 72, 112)); + configuration.AttackMachineZones.Add(this.CreateZone(88, 104, 124, 111)); + configuration.AttackMachineZones.Add(this.CreateZone(116, 105, 124, 112)); + configuration.AttackMachineZones.Add(this.CreateZone(73, 86, 105, 103)); + + configuration.DefenseMachineZones.Add(this.CreateZone(61, 88, 93, 108)); + configuration.DefenseMachineZones.Add(this.CreateZone(92, 89, 127, 111)); + configuration.DefenseMachineZones.Add(this.CreateZone(84, 52, 102, 66)); + } + + private void AddNpc( + CastleSiegeConfiguration configuration, + short monsterNumber, + byte instanceId, + bool isPersisted, + CastleSiegeJoinSide defaultSide, + byte spawnX, + byte spawnY, + Direction direction) + { + var definition = this.Context.CreateNew(); + definition.MonsterDefinition = this.GameConfiguration.Monsters.Single(monster => monster.Number == monsterNumber); + definition.InstanceId = instanceId; + definition.IsPersistedToDatabase = isPersisted; + definition.DefaultSide = defaultSide; + definition.SpawnX = spawnX; + definition.SpawnY = spawnY; + definition.Direction = direction; + configuration.NpcDefinitions.Add(definition); + } + + private void AddUpgrade( + ICollection target, + byte level, + int jewelCount, + int zen, + int value) + { + var upgrade = this.Context.CreateNew(); + upgrade.Level = level; + upgrade.RequiredJewelOfGuardianCount = jewelCount; + upgrade.RequiredZen = zen; + upgrade.Value = value; + target.Add(upgrade); + } + + private CastleSiegeZoneDefinition CreateZone(byte x1, byte y1, byte x2, byte y2) + { + var zone = this.Context.CreateNew(); + zone.X1 = x1; + zone.Y1 = y1; + zone.X2 = x2; + zone.Y2 = y2; + return zone; + } +} diff --git a/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs index 8433836..9f224e4 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/GameConfigurationInitializer.cs @@ -89,6 +89,7 @@ public class GameConfigurationInitializer : GameConfigurationInitializerBase new BloodCastleInitializer(this.Context, this.GameConfiguration).Initialize(); new ChaosCastleInitializer(this.Context, this.GameConfiguration).Initialize(); new HeykelSavasiInitializer(this.Context, this.GameConfiguration).Initialize(); + new CastleSiegeInitializer(this.Context, this.GameConfiguration).Initialize(); } private void CreateJewelMixes() From 6f7e58ff35e0da71aa1f7a66e5317688fc195e9c Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 4 Aug 2026 03:37:37 +0300 Subject: [PATCH 2/4] refactor(castle-siege): drive the cycle on the client's state numbers and persist guilds by id Moves AdaMu's working Castle Siege onto the upstream data model that the previous commit introduced, without changing how the siege plays. State model - CastleSiegePhase is replaced by DataModel's CastleSiegeState, whose values are exactly what the game client's CASTLESIEGE_STATE enum expects. The cycle now runs Idle1(0) -> RegisterGuild(1) -> Ready(6) -> Start(7) -> End(8) -> EndCycle(9) -> Idle1(0). - Idle2(2), RegisterMark(3), Idle3(4) and Notify(5) keep their numbers for client compatibility but are never entered: AdaMu registers guilds directly and has no Mark of Lord step. Guild identity - Guilds are now identified by their persistent Guid instead of by name, so a rename (or a delete and re-create under the same name) can no longer hand castle ownership to the wrong guild. Names are carried alongside only for display and for the packets that send a name to the client. - Interfaces.Guild deliberately has no id and the guild server's short ids are in-memory only, so the persistent id is resolved through the guild name once and cached per process. This avoids adding a method to IGuildServer, which upstream keeps changing. Persistence - The castle owner is stored in the CastleSiegeData row and the registrations in CastleSiegeGuildRegistration rows, replacing the previous plugin-configuration JSON blob. Only the current state and when it started still ride on the plugin configuration, because they have no column in the upstream schema. Castle NPCs - The hard-coded gate, catapult, crown and switch coordinates are gone. They are read from GameConfiguration.CastleSiegeConfiguration, seeded by CastleSiegeInitializer. Definitions flagged IsPersistedToDatabase are the breakable defenses and count towards the throne, which additionally brings in the 4 guardian statues the previous implementation did not spawn. - The crown hold time now comes from the seeded configuration instead of the plugin settings. The AdaMu operational settings (cycle durations, registration fee, designated server id, auto-open schedule) moved to a renamed CastleSiegeSettings class, so they no longer collide with upstream's CastleSiegeConfiguration entity. Verified: full server build succeeds with 0 errors. Not yet done: the 0xB2 0x00 CastleSiegeState request handler, and the docker / local run. --- .../CastleSiege/CastleSiegeContext.cs | 293 ++++++--- .../CastleSiegeGuardsmanTalkPlugIn.cs | 18 +- src/GameLogic/CastleSiege/CastleSiegePhase.cs | 26 - ...onfiguration.cs => CastleSiegeSettings.cs} | 69 +- .../CastleSiegeThroneCaptureTalkPlugIn.cs | 24 +- src/GameLogic/Player.cs | 2 +- .../CastleSiegePhaseChatCommandPlugIn.cs | 8 +- .../CastleSiegeSetOwnerChatCommandPlugIn.cs | 19 +- .../PeriodicTasks/CastleSiegeEventPlugIn.cs | 602 +++++++++++------- 9 files changed, 619 insertions(+), 442 deletions(-) delete mode 100644 src/GameLogic/CastleSiege/CastleSiegePhase.cs rename src/GameLogic/CastleSiege/{CastleSiegeConfiguration.cs => CastleSiegeSettings.cs} (72%) diff --git a/src/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs index 4a5ef2e..1361fb9 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs @@ -4,9 +4,29 @@ namespace MUnique.OpenMU.GameLogic.CastleSiege; +using MUnique.OpenMU.DataModel.Configuration; + /// -/// In-memory Castle Siege phase state machine and battle contention (P3). +/// In-memory Castle Siege state machine and battle contention. /// Time is injected via method parameters so it can be tested deterministically. +/// +/// The cycle uses the original Season 6 values, which are exactly the values +/// the game client expects (see CASTLESIEGE_STATE in the client's WSclient.h). AdaMu drives only +/// a subset of them, because it registers guilds directly and has no Mark of Lord step: +/// +/// +/// Idle1(0) -> RegisterGuild(1) -> Ready(6) -> Start(7) -> End(8) -> EndCycle(9) -> Idle1(0) +/// +/// +/// The skipped states (, , +/// , ) keep their numbers so the +/// client stays compatible; the server simply never enters them. +/// +/// +/// Guilds are identified by their persistent , not by name. A guild rename (or a delete and +/// re-create under the same name) therefore can no longer transfer castle ownership to the wrong guild. Names +/// are carried alongside purely for display and for the packets that send a name to the client. +/// /// Battle rule: attackers must destroy all castle defenses (gates + guardian statues) and then hold BOTH /// Crown Switches at the same time — the switches are held by standing on them (evaluated per tick by the /// plugin), and once both are held by one guild with the defenses down, that guild captures the throne. @@ -17,96 +37,115 @@ public class CastleSiegeContext /// The Crown Switch NPC numbers on Valley of Loren; both must be held to take the throne. public static readonly short[] SwitchNumbers = { 217, 218 }; - private readonly List _registeredGuilds = new(); - private readonly Dictionary _switchHolders = new() { { 217, null }, { 218, null } }; - private DateTime _phaseStartedUtc; - private string? _occupier; + private readonly Dictionary _registeredGuilds = new(); + private readonly Dictionary _switchHolders = new() { { 217, null }, { 218, null } }; + private DateTime _stateStartedUtc; + private Guid? _occupier; + private string? _occupierName; private int _defensesRemaining; private bool _dirty; - private string? _crownHoldGuild; + private Guid? _crownHoldGuild; private DateTime? _crownHoldStartUtc; private bool _lastShieldDown; /// Initializes a new instance of the class. /// The cycle timing configuration. - public CastleSiegeContext(CastleSiegeConfiguration configuration) + public CastleSiegeContext(CastleSiegeSettings configuration) { this.Configuration = configuration; - this.Phase = CastleSiegePhase.Ownership; + this.State = CastleSiegeState.Idle1; } - /// Raised after the phase changes. Argument is the new phase. - public event Action? PhaseChanged; + /// Raised after the state changes. Argument is the new state. + public event Action? StateChanged; /// Gets the configuration (durations + schedule). Refreshed each tick from the live plugin config /// so AdminPanel edits take effect without a restart. - public CastleSiegeConfiguration Configuration { get; private set; } + public CastleSiegeSettings Configuration { get; private set; } - /// Points the context at the current (possibly AdminPanel-edited) plugin configuration. - /// The live configuration. - public void UpdateConfiguration(CastleSiegeConfiguration configuration) => this.Configuration = configuration; + /// Gets the current state. + public CastleSiegeState State { get; private set; } - /// Gets the current phase. - public CastleSiegePhase Phase { get; private set; } + /// Gets the UTC time the current state started (used for persistence/restore). + public DateTime StateStartedUtc => this._stateStartedUtc; - /// Gets the UTC time the current phase started (used for persistence/restore). - public DateTime PhaseStartedUtc => this._phaseStartedUtc; + /// Gets the persistent identifier of the owner guild, or if unowned. + public Guid? OwnerGuildId { get; private set; } - /// Gets the current owner guild name, or null if unowned. + /// Gets the owner guild's name for display and client packets, or null. public string? OwnerGuildName { get; private set; } - /// Gets the guild currently holding the throne during the siege (P3), or null. - public string? OccupierGuildName => this._occupier; + /// Gets the guild currently holding the throne during the siege, or null. + public Guid? OccupierGuildId => this._occupier; + + /// Gets the throne holder's name for display and client packets, or null. + public string? OccupierGuildName => this._occupierName; /// Gets the number of castle defenses (gates + statues) still standing; the throne needs 0. public int DefensesRemaining => this._defensesRemaining; - /// Gets the guild names registered for the current cycle. - public IReadOnlyList RegisteredGuilds => this._registeredGuilds; + /// Gets the persistent identifiers of the guilds registered for the current cycle. + public IReadOnlyCollection RegisteredGuildIds => this._registeredGuilds.Keys; + + /// Gets the names of the guilds registered for the current cycle (display only). + public IReadOnlyCollection RegisteredGuildNames => this._registeredGuilds.Values; + + /// Gets a value indicating whether the siege battle is currently running. + public bool IsSiegeRunning => this.State == CastleSiegeState.Start; + + /// Points the context at the current (possibly AdminPanel-edited) plugin configuration. + /// The live configuration. + public void UpdateConfiguration(CastleSiegeSettings configuration) => this.Configuration = configuration; + + /// Returns whether the given guild is registered for the current cycle. + /// The persistent guild identifier. + public bool IsRegistered(Guid guildId) => this._registeredGuilds.ContainsKey(guildId); /// Advances the state machine based on the current time. /// The current UTC time. public ValueTask TickAsync(DateTime now) { - switch (this.Phase) + switch (this.State) { - case CastleSiegePhase.Ownership: + case CastleSiegeState.Idle1: if (this.Configuration.IsRegistrationOpenTime(now)) { return this.ForceStartRegistrationAsync(now); } break; - case CastleSiegePhase.Registration: - if (now >= this._phaseStartedUtc + this.Configuration.RegistrationDuration) + case CastleSiegeState.RegisterGuild: + if (now >= this._stateStartedUtc + this.Configuration.RegistrationDuration) { - return this.TransitionAsync(CastleSiegePhase.Preparation, now); + return this.TransitionAsync(CastleSiegeState.Ready, now); } break; - case CastleSiegePhase.Preparation: - if (now >= this._phaseStartedUtc + this.Configuration.PreparationDuration) + case CastleSiegeState.Ready: + if (now >= this._stateStartedUtc + this.Configuration.PreparationDuration) { - return this.TransitionAsync(CastleSiegePhase.Siege, now); + return this.TransitionAsync(CastleSiegeState.Start, now); } break; - case CastleSiegePhase.Siege: - if (now >= this._phaseStartedUtc + this.Configuration.SiegeDuration) + case CastleSiegeState.Start: + if (now >= this._stateStartedUtc + this.Configuration.SiegeDuration) { - return this.TransitionAsync(CastleSiegePhase.Settlement, now); + return this.TransitionAsync(CastleSiegeState.End, now); } break; - case CastleSiegePhase.Settlement: + case CastleSiegeState.End: // Winner = guild holding the throne at siege end. If none captured, owner unchanged. if (this._occupier is { } occupier) { - this.SetOwner(occupier); + this.SetOwner(occupier, this._occupierName); } this.ClearBattleState(); - return this.TransitionAsync(CastleSiegePhase.Ownership, now); + return this.TransitionAsync(CastleSiegeState.EndCycle, now); + case CastleSiegeState.EndCycle: + return this.TransitionAsync(CastleSiegeState.Idle1, now); default: break; } @@ -114,62 +153,67 @@ public class CastleSiegeContext return ValueTask.CompletedTask; } - /// Admin: forces the cycle into registration now (from any phase). + /// Admin: forces the cycle into guild registration now (from any state). /// The current UTC time. public ValueTask ForceStartRegistrationAsync(DateTime now) { this._registeredGuilds.Clear(); this.ClearBattleState(); - return this.TransitionAsync(CastleSiegePhase.Registration, now); + return this.TransitionAsync(CastleSiegeState.RegisterGuild, now); } - /// Admin: forces a specific phase now. - /// The target phase. + /// Admin: forces a specific state now. + /// The target state. /// The current UTC time. - public ValueTask ForcePhaseAsync(CastleSiegePhase phase, DateTime now) - => this.TransitionAsync(phase, now); + public ValueTask ForceStateAsync(CastleSiegeState state, DateTime now) + => this.TransitionAsync(state, now); - /// Admin: resets to the ownership (resting) phase and clears registrations/battle state. + /// Admin: resets to the idle (resting) state and clears registrations/battle state. /// The current UTC time. public ValueTask ResetAsync(DateTime now) { this._registeredGuilds.Clear(); this.ClearBattleState(); - return this.TransitionAsync(CastleSiegePhase.Ownership, now); + return this.TransitionAsync(CastleSiegeState.Idle1, now); } - /// Registers a guild (by name) for the current cycle. No-op outside registration. - /// The guild name. - public void RegisterGuild(string guildName) + /// Registers a guild for the current cycle. No-op outside the registration state. + /// The persistent guild identifier. + /// The guild name, for display. + public void RegisterGuild(Guid guildId, string guildName) { - if (this.Phase == CastleSiegePhase.Registration - && !this._registeredGuilds.Contains(guildName)) + if (this.State == CastleSiegeState.RegisterGuild + && this._registeredGuilds.TryAdd(guildId, guildName)) { - this._registeredGuilds.Add(guildName); this._dirty = true; } } - /// Admin: sets (or clears) the current owner guild name. - /// The owner guild name, or null to clear. - public void SetOwner(string? guildName) + /// Admin: sets (or clears) the current owner guild. + /// The owner guild identifier, or null to clear. + /// The owner guild name, or null. + public void SetOwner(Guid? guildId, string? guildName) { - this.OwnerGuildName = guildName; + this.OwnerGuildId = guildId; + this.OwnerGuildName = guildId is null ? null : guildName; this._dirty = true; } /// - /// Mirrors the shared castle owner from the configuration. Used on game servers that do NOT host the + /// Mirrors the shared castle owner loaded from the database. Used on game servers that do NOT host the /// siege, so their hunting-map gate and castle flag still reflect the current owner. Does not mark the /// state dirty (these servers never persist). /// - public void SyncOwnerFromConfig() + /// The owner guild identifier, or null. + /// The owner guild name, or null. + public void SyncOwner(Guid? guildId, string? guildName) { - this.OwnerGuildName = this.Configuration.PersistedOwnerGuildName; + this.OwnerGuildId = guildId; + this.OwnerGuildName = guildId is null ? null : guildName; } /// - /// Sets the weekly auto-schedule (days of week + UTC time) into the configuration and marks the state + /// Sets the auto-open schedule (days of week + UTC time) into the configuration and marks the state /// dirty for persistence. Empty days disables auto-start (manual only). The configuration is the single /// source of truth, so this is equivalent to editing the plugin config in the AdminPanel. /// @@ -192,12 +236,36 @@ public class CastleSiegeContext /// The current UTC time. public TimeSpan GetRemainingSiegeTime(DateTime nowUtc) { - if (this.Phase != CastleSiegePhase.Siege) + if (!this.IsSiegeRunning) { return TimeSpan.Zero; } - var remaining = (this._phaseStartedUtc + this.Configuration.SiegeDuration) - nowUtc; + var remaining = (this._stateStartedUtc + this.Configuration.SiegeDuration) - nowUtc; + return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero; + } + + /// + /// Returns how much time is left in the current state, or when the state has + /// no duration (idle states wait for an admin command or the auto-open time). + /// + /// The current UTC time. + public TimeSpan GetRemainingStateTime(DateTime nowUtc) + { + var duration = this.State switch + { + CastleSiegeState.RegisterGuild => this.Configuration.RegistrationDuration, + CastleSiegeState.Ready => this.Configuration.PreparationDuration, + CastleSiegeState.Start => this.Configuration.SiegeDuration, + _ => TimeSpan.Zero, + }; + + if (duration == TimeSpan.Zero) + { + return TimeSpan.Zero; + } + + var remaining = (this._stateStartedUtc + duration) - nowUtc; return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero; } @@ -205,9 +273,9 @@ public class CastleSiegeContext /// Returns the guild that currently holds BOTH crown switches while all castle defenses are down (so the /// crown's shield is dropped for them), or null. Only meaningful during the siege. /// - public string? GetShieldEligibleGuild() + public Guid? GetShieldEligibleGuild() { - if (this.Phase != CastleSiegePhase.Siege || this._defensesRemaining > 0) + if (!this.IsSiegeRunning || this._defensesRemaining > 0) { return null; } @@ -223,20 +291,21 @@ public class CastleSiegeContext /// Losing a switch or the master leaving the crown resets the hold (contestable until the siege ends). /// /// The guild with both switches and no defenses, or null. + /// That guild's name, for display. /// Whether that guild's master is on the crown. /// The current UTC time. /// How long the master must hold to capture. - public CrownTickResult TickCrownHold(string? eligibleGuild, bool masterHolding, DateTime now, TimeSpan holdDuration) + public CrownTickResult TickCrownHold(Guid? eligibleGuild, string? eligibleGuildName, bool masterHolding, DateTime now, TimeSpan holdDuration) { - var shieldDown = this.Phase == CastleSiegePhase.Siege && eligibleGuild is not null; + var shieldDown = this.IsSiegeRunning && eligibleGuild is not null; var shieldChanged = shieldDown != this._lastShieldDown; this._lastShieldDown = shieldDown; - if (this.Phase != CastleSiegePhase.Siege) + if (!this.IsSiegeRunning) { this._crownHoldGuild = null; this._crownHoldStartUtc = null; - return new CrownTickResult(false, shieldChanged, CrownEvent.None, null); + return new CrownTickResult(false, shieldChanged, CrownEvent.None, null, null); } var wasHolding = this._crownHoldGuild is not null; @@ -249,26 +318,27 @@ public class CastleSiegeContext { this._crownHoldGuild = null; this._crownHoldStartUtc = null; - return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null); + return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null, null); } if (this._crownHoldGuild != eligibleGuild || this._crownHoldStartUtc is null) { this._crownHoldGuild = eligibleGuild; this._crownHoldStartUtc = now; - return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.HoldStarted, eligibleGuild); + return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.HoldStarted, eligibleGuild, eligibleGuildName); } if (now - this._crownHoldStartUtc.Value >= holdDuration) { this._occupier = eligibleGuild; + this._occupierName = eligibleGuildName; this._crownHoldGuild = null; this._crownHoldStartUtc = null; this._dirty = true; - return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild); + return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild, eligibleGuildName); } - return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.None, eligibleGuild); + return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.None, eligibleGuild, eligibleGuildName); } /// @@ -283,22 +353,27 @@ public class CastleSiegeContext } /// - /// Restores persisted state on startup (owner, phase, phase-start, registrations) directly, without - /// firing or marking the state dirty. Battle state stays cleared. + /// Restores persisted state on startup (owner, state, state-start, registrations) directly, without + /// firing or marking the state dirty. Battle state stays cleared. /// - /// The persisted owner guild name, or null. - /// The persisted phase. - /// When the persisted phase started (UTC), or null to keep the default. - /// The persisted registered guild names, or null. - public void RestoreState(string? owner, CastleSiegePhase phase, DateTime? phaseStartedUtc, IEnumerable? registeredGuilds) + /// The persisted owner guild identifier, or null. + /// The persisted owner guild name, or null. + /// The persisted state. + /// When the persisted state started (UTC), or null to keep the default. + /// The persisted registrations (id to name), or null. + public void RestoreState(Guid? ownerGuildId, string? ownerGuildName, CastleSiegeState state, DateTime? stateStartedUtc, IEnumerable>? registeredGuilds) { - this.OwnerGuildName = owner; - this.Phase = phase; - this._phaseStartedUtc = phaseStartedUtc ?? this._phaseStartedUtc; + this.OwnerGuildId = ownerGuildId; + this.OwnerGuildName = ownerGuildId is null ? null : ownerGuildName; + this.State = state; + this._stateStartedUtc = stateStartedUtc ?? this._stateStartedUtc; this._registeredGuilds.Clear(); if (registeredGuilds is not null) { - this._registeredGuilds.AddRange(registeredGuilds); + foreach (var registration in registeredGuilds) + { + this._registeredGuilds[registration.Key] = registration.Value; + } } this._dirty = false; @@ -322,12 +397,12 @@ public class CastleSiegeContext /// based on player positions. Pass null when no registered member stands on it. No-op outside the siege. /// /// The Crown Switch NPC number (217 or 218). - /// The holding guild's name, or null. - public void SetSwitchHolder(short switchNumber, string? guildName) + /// The holding guild's persistent identifier, or null. + public void SetSwitchHolder(short switchNumber, Guid? guildId) { - if (this.Phase == CastleSiegePhase.Siege && this._switchHolders.ContainsKey(switchNumber)) + if (this.IsSiegeRunning && this._switchHolders.ContainsKey(switchNumber)) { - this._switchHolders[switchNumber] = guildName; + this._switchHolders[switchNumber] = guildId; } } @@ -336,20 +411,21 @@ public class CastleSiegeContext /// Succeeds only during the siege when the throne is free, all castle defenses are destroyed, and the /// guild is currently holding BOTH Crown Switches (a member standing on each). /// - /// The capturing guild's name. + /// The capturing guild's persistent identifier. + /// The capturing guild's name, for display. /// Whether it succeeded and a human-readable reason/result message. - public (bool Success, string Reason) TryCaptureThrone(string guildName) + public (bool Success, string Reason) TryCaptureThrone(Guid guildId, string guildName) { - if (this.Phase != CastleSiegePhase.Siege) + if (!this.IsSiegeRunning) { return (false, "The siege is not running."); } - if (this._occupier is not null) + if (this._occupier is { } occupier) { - return (false, this._occupier == guildName + return (false, occupier == guildId ? "Your guild already holds the throne." - : $"The throne is already held by '{this._occupier}'."); + : $"The throne is already held by '{this._occupierName ?? occupier.ToString()}'."); } if (this._defensesRemaining > 0) @@ -357,21 +433,28 @@ public class CastleSiegeContext return (false, $"Destroy the castle defenses first ({this._defensesRemaining} remaining)."); } - if (this._switchHolders[217] != guildName || this._switchHolders[218] != guildName) + if (this._switchHolders[217] != guildId || this._switchHolders[218] != guildId) { return (false, "Your guild must be holding BOTH Crown Switches at once (stand a member on each)."); } - this._occupier = guildName; + this._occupier = guildId; + this._occupierName = guildName; + this._dirty = true; return (true, "throne captured"); } /// Returns a human-readable status summary for admin display. public string GetStatusText() - => $"CS phase={this.Phase}, owner={this.OwnerGuildName ?? "(none)"}, " - + $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds)}], " - + $"defenses={this._defensesRemaining}, throne={this._occupier ?? "(none)"}, " - + $"switch217={this._switchHolders[217] ?? "-"}, switch218={this._switchHolders[218] ?? "-"}"; + => $"CS state={this.State}({(int)this.State}), owner={this.OwnerGuildName ?? "(none)"}, " + + $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds.Values)}], " + + $"defenses={this._defensesRemaining}, throne={this._occupierName ?? "(none)"}, " + + $"switch217={this.DescribeSwitch(217)}, switch218={this.DescribeSwitch(218)}"; + + private string DescribeSwitch(short switchNumber) + => this._switchHolders[switchNumber] is { } holder + ? (this._registeredGuilds.TryGetValue(holder, out var name) ? name : holder.ToString()) + : "-"; private void ClearBattleState() { @@ -379,17 +462,18 @@ public class CastleSiegeContext this._switchHolders[218] = null; this._defensesRemaining = 0; this._occupier = null; + this._occupierName = null; this._crownHoldGuild = null; this._crownHoldStartUtc = null; this._lastShieldDown = false; } - private ValueTask TransitionAsync(CastleSiegePhase phase, DateTime now) + private ValueTask TransitionAsync(CastleSiegeState state, DateTime now) { - this.Phase = phase; - this._phaseStartedUtc = now; + this.State = state; + this._stateStartedUtc = now; this._dirty = true; - this.PhaseChanged?.Invoke(phase); + this.StateChanged?.Invoke(state); return ValueTask.CompletedTask; } } @@ -414,5 +498,6 @@ public enum CrownEvent /// Whether the crown shield is currently down (both switches held, defenses cleared). /// Whether the shield state changed this tick (only then should the client be told). /// The event that occurred this tick. -/// The guild the event refers to, if any. -public readonly record struct CrownTickResult(bool ShieldDown, bool ShieldChanged, CrownEvent Event, string? Guild); +/// The guild the event refers to, if any. +/// That guild's name, for display and client packets. +public readonly record struct CrownTickResult(bool ShieldDown, bool ShieldChanged, CrownEvent Event, Guid? GuildId, string? GuildName); diff --git a/src/GameLogic/CastleSiege/CastleSiegeGuardsmanTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeGuardsmanTalkPlugIn.cs index a9e5a48..c212ac7 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeGuardsmanTalkPlugIn.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeGuardsmanTalkPlugIn.cs @@ -49,7 +49,7 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn return; } - if (context.Phase != CastleSiegePhase.Registration) + if (context.State != CastleSiegeState.RegisterGuild) { await ShowAsync(player, "Castle Siege registration is not open right now.").ConfigureAwait(false); return; @@ -61,17 +61,15 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn return; } - var guildName = guildStatus.GuildId.ToString(); - if (player.GameContext is IGameServerContext serverContext) + // Registrations are keyed on the guild's persistent id, so a later rename cannot detach them. + if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild) { - var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); - if (guild?.Name is { Length: > 0 } name) - { - guildName = name; - } + await ShowAsync(player, "Your guild could not be resolved. Please try again in a moment.").ConfigureAwait(false); + return; } - if (context.RegisteredGuilds.Contains(guildName)) + var guildName = guild.Name; + if (context.IsRegistered(guild.Id)) { await ShowAsync(player, $"Your guild '{guildName}' is already registered for the Castle Siege.").ConfigureAwait(false); return; @@ -84,7 +82,7 @@ public class CastleSiegeGuardsmanTalkPlugIn : IPlayerTalkToNpcPlugIn return; } - context.RegisterGuild(guildName); + context.RegisterGuild(guild.Id, guildName); await ShowAsync(player, fee > 0 ? $"Your guild '{guildName}' is registered for the Castle Siege. ({fee} zen paid)" : $"Your guild '{guildName}' is registered for the Castle Siege.").ConfigureAwait(false); diff --git a/src/GameLogic/CastleSiege/CastleSiegePhase.cs b/src/GameLogic/CastleSiege/CastleSiegePhase.cs deleted file mode 100644 index 0317868..0000000 --- a/src/GameLogic/CastleSiege/CastleSiegePhase.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace MUnique.OpenMU.GameLogic.CastleSiege; - -/// -/// The phases of a Castle Siege cycle. -/// -public enum CastleSiegePhase -{ - /// Resting phase: castle is (un)owned, waiting for the next registration window. - Ownership, - - /// Guilds can register to attack. - Registration, - - /// Registration closed; defenders prepare before the siege starts. - Preparation, - - /// The siege battle is running. - Siege, - - /// Siege ended; determining the new owner. - Settlement, -} diff --git a/src/GameLogic/CastleSiege/CastleSiegeConfiguration.cs b/src/GameLogic/CastleSiege/CastleSiegeSettings.cs similarity index 72% rename from src/GameLogic/CastleSiege/CastleSiegeConfiguration.cs rename to src/GameLogic/CastleSiege/CastleSiegeSettings.cs index dfe54a0..a7bd6f4 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeConfiguration.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeSettings.cs @@ -1,4 +1,4 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -7,15 +7,31 @@ namespace MUnique.OpenMU.GameLogic.CastleSiege; using System.ComponentModel; using System.Linq; using System.Text.Json.Serialization; +using MUnique.OpenMU.DataModel.Configuration; /// -/// Configuration for the Castle Siege cycle. Rides on the plugin custom-configuration system (no dedicated -/// database table). A cycle runs: Ownership -> Registration -> Preparation -> Siege(war) -> Settlement, and -/// auto-starts when the current day/time matches + . -/// The AdminPanel-friendly properties (OpenDays checkboxes, minute/second durations) are proxies over the -/// runtime fields, which are hidden from the editor to keep the form clean. +/// AdaMu operational settings for the Castle Siege cycle. Rides on the plugin custom-configuration system, so +/// it is editable in the AdminPanel and needs no dedicated database table. +/// +/// This is deliberately separate from , which is +/// the upstream, database-backed configuration holding the NPC/zone/upgrade definitions and the crown hold +/// time. Keeping AdaMu's operational knobs out of that entity means upstream schema changes apply cleanly and +/// no hand-editing of the generated persistence code is needed. +/// +/// +/// What lives where: +/// +/// Castle owner and guild registrations: database (CastleSiegeData, CastleSiegeGuildRegistration). +/// NPC/zone/upgrade definitions and crown hold time: database (GameConfiguration.CastleSiegeConfiguration). +/// Cycle durations, registration fee, designated server and the current state: here. +/// +/// +/// A cycle runs Idle1 -> RegisterGuild -> Ready -> Start -> End -> EndCycle -> Idle1, and auto-starts when the +/// current day/time matches + . +/// The AdminPanel-friendly properties (OpenDays checkboxes, minute durations) are proxies over the runtime +/// fields, which are hidden from the editor to keep the form clean. /// -public class CastleSiegeConfiguration +public class CastleSiegeSettings { /// /// Gets or sets the days of week on which registration auto-opens (UTC). None = every day (still needs a @@ -42,7 +58,7 @@ public class CastleSiegeConfiguration /// /// Gets or sets the times of day (UTC) at which a new cycle opens registration. - /// Empty = no auto-start (admins start cycles manually via the chat command). + /// Empty = no auto-start (admins start cycles manually via the chat command or the AdminPanel). /// public IList RegistrationOpenTimes { get; set; } = new List(); @@ -70,17 +86,6 @@ public class CastleSiegeConfiguration set => this.SiegeDuration = TimeSpan.FromMinutes(Math.Max(1, value)); } - /// - /// Gets or sets how long the guild master must hold the Crown to capture the throne, in seconds. - /// The client shows a 60-second countdown, so 60 matches the on-screen timer. - /// - [JsonIgnore] - public int CrownHoldSeconds - { - get => (int)this.CrownHoldDuration.TotalSeconds; - set => this.CrownHoldDuration = TimeSpan.FromSeconds(Math.Max(1, value)); - } - /// /// Gets or sets the registration fee (in zen) a guild master must pay to register the guild /// for the siege. 0 disables the fee. @@ -112,29 +117,17 @@ public class CastleSiegeConfiguration [Browsable(false)] public TimeSpan SiegeDuration { get; set; } = TimeSpan.FromMinutes(10); - /// Gets or sets how long the guild master must hold the Crown to capture the throne. - [Browsable(false)] - public TimeSpan CrownHoldDuration { get; set; } = TimeSpan.FromSeconds(60); + // --- Persisted cycle bookkeeping (hidden from the AdminPanel) --- + // Only the CURRENT state and when it started ride on the plugin's custom-configuration JSON. The castle + // owner and the guild registrations live in real database tables, so they are not duplicated here. - // --- Persisted runtime state (hidden from the AdminPanel) --- - // These ride on the plugin's custom-configuration JSON (stored in PostgreSQL), so the castle owner and the - // current cycle survive server restarts. Written by CastleSiegeEventPlugIn; restored on startup. - - /// Gets or sets the persisted castle owner guild name (null = unowned). + /// Gets or sets the persisted current state, so the cycle resumes after a restart. [Browsable(false)] - public string? PersistedOwnerGuildName { get; set; } + public CastleSiegeState PersistedState { get; set; } = CastleSiegeState.Idle1; - /// Gets or sets the persisted current phase, so the cycle resumes after a restart. + /// Gets or sets when the persisted state started (UTC), or null if never persisted. [Browsable(false)] - public CastleSiegePhase PersistedPhase { get; set; } = CastleSiegePhase.Ownership; - - /// Gets or sets when the persisted phase started (UTC), or null if never persisted. - [Browsable(false)] - public DateTime? PersistedPhaseStartedUtc { get; set; } - - /// Gets or sets the persisted registered guild names for the current cycle. - [Browsable(false)] - public IList PersistedRegisteredGuilds { get; set; } = new List(); + public DateTime? PersistedStateStartedUtc { get; set; } /// /// Returns true if (UTC) matches a scheduled registration-open day and falls diff --git a/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs index 891af70..b6fca0b 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs @@ -43,35 +43,25 @@ public class CastleSiegeThroneCaptureTalkPlugIn : IPlayerTalkToNpcPlugIn return; } - if (player.GuildStatus is not { } guildStatus) + if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild) { await ShowAsync(player, "Only members of a registered guild can take the throne.").ConfigureAwait(false); return; } - var guildName = guildStatus.GuildId.ToString(); - if (player.GameContext is IGameServerContext serverContext) - { - var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); - if (guild?.Name is { Length: > 0 } name) - { - guildName = name; - } - } - - if (!context.RegisteredGuilds.Contains(guildName)) + if (!context.IsRegistered(guild.Id)) { await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false); return; } // The throne is taken by holding the Crown, not by talking here — give guidance based on the state. - await ShowAsync(player, DescribeThroneStep(context, guildName)).ConfigureAwait(false); + await ShowAsync(player, DescribeThroneStep(context, guild.Id)).ConfigureAwait(false); } - private static string DescribeThroneStep(CastleSiegeContext context, string guildName) + private static string DescribeThroneStep(CastleSiegeContext context, Guid guildId) { - if (context.Phase != CastleSiegePhase.Siege) + if (!context.IsSiegeRunning) { return "The siege is not running yet."; } @@ -87,12 +77,12 @@ public class CastleSiegeThroneCaptureTalkPlugIn : IPlayerTalkToNpcPlugIn return "All gates are down! Hold BOTH Crown Switches with your guild — the Crown's shield will drop."; } - if (eligible == guildName) + if (eligible == guildId) { return "Your guild holds both switches and the shield is down — send your GUILD MASTER to hold the Crown to take the throne!"; } - return $"Guild '{eligible}' is holding both switches. Take a switch back to raise their shield."; + return "Another guild is holding both switches. Take a switch back to raise their shield."; } private static ValueTask ShowAsync(Player player, string text) diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index 64a7da6..5b9928b 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -717,7 +717,7 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke // ADAMU-CUSTOM: Castle Siege PvP gate — true while the siege phase runs on Valley of Loren (map 30). private bool IsCastleSiegeBattleActive() => this.CurrentMap?.Definition.Number == 30 - && PlugIns.PeriodicTasks.CastleSiegeEventPlugIn.TryGetContext(this.GameContext)?.Phase == CastleSiege.CastleSiegePhase.Siege; + && PlugIns.PeriodicTasks.CastleSiegeEventPlugIn.TryGetContext(this.GameContext)?.IsSiegeRunning == true; /// public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null) diff --git a/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs b/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs index 3ea4095..588fff7 100644 --- a/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs +++ b/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs @@ -30,9 +30,11 @@ public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn public async ValueTask HandleCommandAsync(Player player, string command) { var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length < 2 || !Enum.TryParse(parts[1], true, out var phase)) + // The cycle uses the original Season 6 state names the client knows. AdaMu drives only this subset; + // Idle2, RegisterMark, Idle3 and Notify exist for client compatibility but are never entered. + if (parts.Length < 2 || !Enum.TryParse(parts[1], true, out var phase)) { - await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync("Usage: /csphase ", MessageType.BlueNormal)).ConfigureAwait(false); + await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync("Usage: /csphase ", MessageType.BlueNormal)).ConfigureAwait(false); return; } @@ -43,7 +45,7 @@ public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn return; } - await context.ForcePhaseAsync(phase, DateTime.UtcNow).ConfigureAwait(false); + await context.ForceStateAsync(phase, DateTime.UtcNow).ConfigureAwait(false); await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync($"Castle Siege: phase set to {phase}.", MessageType.BlueNormal)).ConfigureAwait(false); } } diff --git a/src/GameLogic/PlugIns/ChatCommands/CastleSiegeSetOwnerChatCommandPlugIn.cs b/src/GameLogic/PlugIns/ChatCommands/CastleSiegeSetOwnerChatCommandPlugIn.cs index 5f1e4e6..e389b93 100644 --- a/src/GameLogic/PlugIns/ChatCommands/CastleSiegeSetOwnerChatCommandPlugIn.cs +++ b/src/GameLogic/PlugIns/ChatCommands/CastleSiegeSetOwnerChatCommandPlugIn.cs @@ -38,7 +38,22 @@ public class CastleSiegeSetOwnerChatCommandPlugIn : IChatCommandPlugIn return; } - context.SetOwner(string.IsNullOrWhiteSpace(owner) ? null : owner); - await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync($"Castle Siege owner set to {owner ?? "(none)"}.", MessageType.BlueNormal)).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(owner)) + { + context.SetOwner(null, null); + await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync("Castle Siege owner cleared.", MessageType.BlueNormal)).ConfigureAwait(false); + return; + } + + // Ownership is stored by the guild's persistent id, so the name given here is resolved once. + var guildId = await CastleSiegeEventPlugIn.ResolveGuildIdByNameAsync(player.GameContext, owner).ConfigureAwait(false); + if (guildId is not { } id) + { + await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync($"No guild named '{owner}' was found.", MessageType.BlueNormal)).ConfigureAwait(false); + return; + } + + context.SetOwner(id, owner); + await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync($"Castle Siege owner set to {owner}.", MessageType.BlueNormal)).ConfigureAwait(false); } } diff --git a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs index 93c65d3..486c222 100644 --- a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs +++ b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs @@ -7,6 +7,7 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; using System.Collections.Concurrent; using System.Linq; using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic.CastleSiege; using MUnique.OpenMU.GameLogic.NPC; using MUnique.OpenMU.GameLogic.Views; @@ -15,65 +16,50 @@ using MUnique.OpenMU.Interfaces; using MUnique.OpenMU.Pathfinding; using MUnique.OpenMU.Persistence; using MUnique.OpenMU.PlugIns; +using CastleSiegeDefinition = MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration; /// -/// Drives the Castle Siege phase state machine: ticks it every second and carries its configuration. -/// State is per- and kept in memory (P1: no persistence). -/// When the siege phase starts (P3), warps registered guild members to the Valley of Loren battle map. +/// Drives the Castle Siege state machine: ticks it every second and carries its operational settings. +/// +/// The cycle uses the original Season 6 state numbers the client expects. The castle owner and the guild +/// registrations are persisted in real database tables ( and +/// ) and keyed by the guild's persistent , so a +/// guild rename can no longer move castle ownership to the wrong guild. Only the current state and when it +/// started ride on the plugin's custom-configuration JSON. +/// +/// +/// Castle NPCs (gates, statues, catapults, the crown and its switches) are read from +/// , which the CastleSiegeInitializer seeds, instead +/// of being hard-coded here. +/// +/// When the siege starts, registered guild members are warped to the Valley of Loren battle map. /// [PlugIn] -[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (phase state machine, scheduling, siege warp).")] +[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (state machine, scheduling, siege warp).")] [Guid("6E2C8B41-9A4D-4C2E-9E7B-1F2A3B4C5D60")] -public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration, ISupportDefaultCustomConfiguration +public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration, ISupportDefaultCustomConfiguration { private const ushort ValleyOfLorenMapNumber = 30; - private const short CastleGateNumber = 277; // client MONSTER_CASTLE_GATE1 -> renders a real gate + blocks terrain until broken - private const short GateTemplateNumber = 131; // BloodCastle "Castle Gate" destructible (has HP) — HP template for 277 + private const short GateTemplateNumber = 131; // BloodCastle "Castle Gate" destructible (has HP) — HP template for the CS gates. + private const short CrownNumber = 216; + private const short CatapultAttackNumber = 221; // client MONSTER_SLINGSHOT_ATTACK + private const short CatapultDefenseNumber = 222; // client MONSTER_SLINGSHOT_DEFENSE private const int SwitchHoldRange = 3; - - // Castle defenses spawned at siege start — the 6 REAL castle gates (client g_byGateLocation). - // The client renders them closed and blocks the terrain until each is broken. All must fall before the throne. - private static readonly (short Number, byte X, byte Y)[] DefenseSpawns = - { - (CastleGateNumber, 67, 114), - (CastleGateNumber, 93, 114), - (CastleGateNumber, 119, 114), - (CastleGateNumber, 81, 161), - (CastleGateNumber, 107, 161), - (CastleGateNumber, 93, 204), - }; - - // Siege weapons spawned at siege start purely for war atmosphere. The client renders monster 221/222 as - // catapults (attacker/defender). They are NOT counted as defenses (destroying them doesn't open the throne). - private const short CatapultAttackNumber = 221; // client MONSTER_SLINGSHOT_ATTACK - private const short CatapultDefenseNumber = 222; // client MONSTER_SLINGSHOT_DEFENSE - private static readonly (short Number, byte X, byte Y)[] CatapultSpawns = - { - (CatapultDefenseNumber, 80, 140), - (CatapultDefenseNumber, 110, 140), - (CatapultDefenseNumber, 93, 178), - (CatapultAttackNumber, 74, 100), - (CatapultAttackNumber, 112, 100), - }; - - // Crown Switch positions on Valley of Loren (from the map init) — held by standing on them: (number, x, y). - private static readonly (short SwitchNumber, byte X, byte Y)[] SwitchPositions = - { - (217, 167, 194), - (218, 184, 195), - }; - - // The Crown (NPC 216) position on Valley of Loren — the guild master holds it here to capture the throne. - private static readonly Point CrownPosition = new(176, 212); private const int CrownHoldRange = 4; private static readonly ConcurrentDictionary Contexts = new(); - private string? _cachedFlagOwner; + /// + /// Maps the in-memory guild id (assigned by the guild server, not stable across restarts) to the guild's + /// persistent identifier. Populated lazily; a miss costs one database lookup per guild per process. + /// + private static readonly ConcurrentDictionary PersistentGuildIds = new(); + + private Guid? _cachedFlagOwner; private byte[]? _cachedFlagLogo; /// - public CastleSiegeConfiguration? Configuration { get; set; } + public CastleSiegeSettings? Configuration { get; set; } /// /// Gets the Castle Siege context for a game context, if the periodic tick has initialized it. @@ -99,41 +85,80 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom return serverContext.Id == context.Configuration.CastleSiegeServerId; } + /// + /// Resolves the persistent identifier of the player's guild, or when the player is + /// not in a guild or the guild cannot be resolved. + /// + /// + /// deliberately carries no id: the guild server assigns short ids in memory + /// only. The persistent is therefore resolved through the guild name and cached, which + /// avoids adding a method to that upstream would keep changing. + /// + /// The player. + public static async ValueTask<(Guid Id, string Name)?> GetPersistentGuildAsync(Player player) + { + if (player.GuildStatus is not { } guildStatus + || player.GameContext is not IGameServerContext serverContext) + { + return null; + } + + var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); + if (guild?.Name is not { Length: > 0 } name) + { + return null; + } + + if (PersistentGuildIds.TryGetValue(guildStatus.GuildId, out var cached)) + { + return (cached, name); + } + + var resolved = await ResolveGuildIdByNameAsync(player.GameContext, name).ConfigureAwait(false); + if (resolved is not { } id) + { + return null; + } + + PersistentGuildIds[guildStatus.GuildId] = id; + return (id, name); + } + /// - public object CreateDefaultConfig() => new CastleSiegeConfiguration(); + public object CreateDefaultConfig() => new CastleSiegeSettings(); /// public async ValueTask ExecuteTaskAsync(GameContext gameContext) { var context = Contexts.GetOrAdd(gameContext, gc => { - var config = this.Configuration ?? new CastleSiegeConfiguration(); - var created = new CastleSiegeContext(config); + var settings = this.Configuration ?? new CastleSiegeSettings(); + var created = new CastleSiegeContext(settings); - // Restore state persisted before the last restart (owner/phase/registrations) BEFORE subscribing, - // so restoring doesn't announce phases or re-spawn defenses. - created.RestoreState(config.PersistedOwnerGuildName, config.PersistedPhase, config.PersistedPhaseStartedUtc, config.PersistedRegisteredGuilds); + // Restore the cycle bookkeeping BEFORE subscribing, so restoring doesn't announce states or + // re-spawn defenses. The owner and registrations are loaded from the database right after. + created.RestoreState(null, null, settings.PersistedState, settings.PersistedStateStartedUtc, null); + _ = LoadPersistedStateAsync(gc, created); - // Announce phase changes to the whole server and, when the siege begins, warp registered members. - created.PhaseChanged += phase => _ = OnPhaseChangedAsync(gc, created, phase); + created.StateChanged += state => _ = OnStateChangedAsync(gc, created, state); return created; }); - // Point the context at the current (possibly AdminPanel-edited) config so schedule/durations are live. - if (this.Configuration is { } liveConfig) + // Point the context at the current (possibly AdminPanel-edited) settings so schedule/durations are live. + if (this.Configuration is { } liveSettings) { - context.UpdateConfiguration(liveConfig); + context.UpdateConfiguration(liveSettings); } // With multiple game servers each has its own map instances, so the siege must run on ONE designated // server (CastleSiegeServerId). Other servers skip the siege entirely — they only mirror the shared - // castle owner from the config so the hunting-map gate + castle flag rewards still work everywhere. + // castle owner from the database so the hunting-map gate + castle flag rewards still work everywhere. if (!IsCastleSiegeServer(gameContext)) { - context.SyncOwnerFromConfig(); if (DateTime.UtcNow.Second % 15 == 0) { + await LoadPersistedStateAsync(gameContext, context).ConfigureAwait(false); await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); } @@ -143,18 +168,18 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom await context.TickAsync(DateTime.UtcNow).ConfigureAwait(false); // During the siege, evaluate the Crown Switches (held by standing on them) and the throne capture every tick. - if (context.Phase == CastleSiegePhase.Siege) + if (context.IsSiegeRunning) { await ProcessSiegeTickAsync(gameContext, context).ConfigureAwait(false); } - // Persist owner/phase/registrations to the database whenever they changed, so they survive a restart. + // Persist owner/state/registrations whenever they changed, so they survive a restart. if (context.ConsumeDirty()) { await this.PersistStateAsync(gameContext, context).ConfigureAwait(false); } - // Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any phase). + // Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any state). if (DateTime.UtcNow.Second % 15 == 0) { await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); @@ -171,25 +196,95 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } } - private static async Task OnPhaseChangedAsync(IGameContext gameContext, CastleSiegeContext context, CastleSiegePhase phase) + /// Gets the seeded Castle Siege definition, or when it was not initialized. + /// The game context. + private static CastleSiegeDefinition? GetDefinition(IGameContext gameContext) + => gameContext.Configuration.CastleSiegeConfiguration; + + /// Resolves a guild's persistent identifier from its name, or null when there is no such guild. + /// The game context. + /// The guild name. + internal static async ValueTask ResolveGuildIdByNameAsync(IGameContext gameContext, string guildName) { try { - switch (phase) + using var context = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(DataModel.Entities.Guild), false, gameContext.Configuration); + var guilds = await context.GetAsync().ConfigureAwait(false); + return guilds.FirstOrDefault(guild => guild.Name == guildName)?.Id; + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: could not resolve the persistent id of guild '{guildName}'.", guildName); + return null; + } + } + + private static async ValueTask ResolveGuildNameByIdAsync(IGameContext gameContext, Guid guildId) + { + try + { + using var context = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(DataModel.Entities.Guild), false, gameContext.Configuration); + var guild = await context.GetByIdAsync(guildId).ConfigureAwait(false); + return guild?.Name; + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: could not resolve the name of guild {guildId}.", guildId); + return null; + } + } + + /// Loads the persisted castle owner and guild registrations from the database into the context. + private static async ValueTask LoadPersistedStateAsync(IGameContext gameContext, CastleSiegeContext context) + { + try + { + using var dataContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeData), false, gameContext.Configuration); + var data = (await dataContext.GetAsync().ConfigureAwait(false)).FirstOrDefault(); + + Guid? ownerId = data?.IsOccupied == true ? data.OwnerGuildId : null; + var ownerName = ownerId is { } id ? await ResolveGuildNameByIdAsync(gameContext, id).ConfigureAwait(false) : null; + + using var registrationContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeGuildRegistration), false, gameContext.Configuration); + var registrations = await registrationContext.GetAsync().ConfigureAwait(false); + + var restored = new List>(); + foreach (var registration in registrations) { - case CastleSiegePhase.Registration: + var name = await ResolveGuildNameByIdAsync(gameContext, registration.GuildId).ConfigureAwait(false); + restored.Add(new KeyValuePair(registration.GuildId, name ?? registration.GuildId.ToString())); + } + + context.RestoreState(ownerId, ownerName, context.State, context.StateStartedUtc, restored); + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: error while loading the persisted state."); + } + } + + private static async Task OnStateChangedAsync(IGameContext gameContext, CastleSiegeContext context, CastleSiegeState state) + { + try + { + switch (state) + { + case CastleSiegeState.RegisterGuild: await AnnounceAsync(gameContext, "Castle Siege registration is now open! Guild masters, register at the Guardsman in the Valley of Loren.").ConfigureAwait(false); break; - case CastleSiegePhase.Siege: + case CastleSiegeState.Start: await AnnounceAsync(gameContext, "The Castle Siege has begun! Break the castle gates and guardian statues, then hold BOTH Crown Switches to take the throne!").ConfigureAwait(false); await SpawnCastleDefensesAsync(gameContext, context).ConfigureAwait(false); await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false); break; - case CastleSiegePhase.Settlement: + case CastleSiegeState.End: // Stop the on-map countdown for everyone still on the battle map. await BroadcastSiegeStateAsync(gameContext, false, 0, 0).ConfigureAwait(false); break; - case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner: + case CastleSiegeState.Idle1 when context.OwnerGuildName is { } owner: await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false); break; default: @@ -199,52 +294,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom catch (Exception ex) { gameContext.LoggerFactory.CreateLogger() - .LogError(ex, "Castle Siege: error handling phase change to {phase}.", phase); - } - } - - private async ValueTask PersistStateAsync(GameContext gameContext, CastleSiegeContext context) - { - try - { - if (this.Configuration is not { } config) - { - return; - } - - // Snapshot the live context into the persisted config fields. - config.PersistedOwnerGuildName = context.OwnerGuildName; - config.PersistedPhase = context.Phase; - config.PersistedPhaseStartedUtc = context.PhaseStartedUtc; - config.PersistedRegisteredGuilds = context.RegisteredGuilds.ToList(); - - // Find our plugin-configuration row via the in-memory config graph to get its id. - var pluginTypeId = typeof(CastleSiegeEventPlugIn).GUID; - var inMemory = gameContext.Configuration.PlugInConfigurations.FirstOrDefault(c => c.TypeId == pluginTypeId); - if (inMemory is null) - { - return; - } - - // Load a fresh, change-tracked copy of that row in its own (non-caching) context, rewrite its - // custom-configuration JSON, and save — this is what actually persists to PostgreSQL. - using var ctx = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(PlugInConfiguration), false, gameContext.Configuration); - var row = await ctx.GetByIdAsync(inMemory.GetId()).ConfigureAwait(false); - if (row is null) - { - return; - } - - row.SetConfiguration(config, gameContext.PlugInManager.CustomConfigReferenceHandler); - await ctx.SaveChangesAsync().ConfigureAwait(false); - - gameContext.LoggerFactory.CreateLogger() - .LogInformation("Castle Siege: persisted state (owner={owner}, phase={phase}).", config.PersistedOwnerGuildName ?? "(none)", config.PersistedPhase); - } - catch (Exception ex) - { - gameContext.LoggerFactory.CreateLogger() - .LogError(ex, "Castle Siege: error while persisting state to the database."); + .LogError(ex, "Castle Siege: error handling state change to {state}.", state); } } @@ -269,52 +319,36 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } } + /// + /// Spawns the castle defenses from the seeded NPC definitions. Definitions flagged + /// are the breakable defenses (gates and + /// guardian statues) and are counted towards the throne; the catapults are pure war atmosphere. + /// private static async Task SpawnCastleDefensesAsync(IGameContext gameContext, CastleSiegeContext context) { try { - if (gameContext is not GameContext concrete) - { - context.SetDefenseCount(0); - return; - } - - var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); - if (map is null) + if (gameContext is not GameContext concrete + || GetDefinition(gameContext) is not { } definition + || await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map) { context.SetDefenseCount(0); return; } + var template = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GateTemplateNumber); var spawned = 0; - for (var i = 0; i < DefenseSpawns.Length; i++) + var index = 0; + foreach (var npc in definition.NpcDefinitions.Where(n => n.IsPersistedToDatabase && n.MonsterDefinition is not null)) { - var spawn = DefenseSpawns[i]; - var definition = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == spawn.Number); - if (definition is null) - { - continue; - } - - // The stock CS gate (277) definition has no HP and isn't destructible; make it breakable + // The stock CS gate/statue definitions have no HP and aren't destructible; make them breakable // by borrowing the HP/attributes of a working destructible (BloodCastle gate 131). - var template = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GateTemplateNumber); - EnsureDestructible(definition, template); + EnsureDestructible(npc.MonsterDefinition!, template); - var spawnArea = new MonsterSpawnArea - { - MonsterDefinition = definition, - Quantity = 1, - X1 = spawn.X, - X2 = spawn.X, - Y1 = spawn.Y, - Y2 = spawn.Y, - Direction = Direction.South, - SpawnTrigger = SpawnTrigger.OnceAtEventStart, - }; - - var npc = await concrete.MapInitializer.InitializeSpawnAsync(1000 + i, map, spawnArea).ConfigureAwait(false); - if (npc is AttackableNpcBase attackable) + var monster = await concrete.MapInitializer + .InitializeSpawnAsync(1000 + index++, map, CreateSpawnArea(npc)) + .ConfigureAwait(false); + if (monster is AttackableNpcBase attackable) { attackable.Died += (_, _) => context.NotifyDefenseDestroyed(); spawned++; @@ -323,29 +357,12 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom context.SetDefenseCount(spawned); - // Spawn the catapults (siege weapons) for war atmosphere — not counted as defenses. - for (var i = 0; i < CatapultSpawns.Length; i++) + index = 0; + foreach (var npc in definition.NpcDefinitions.Where(IsCatapult)) { - var spawn = CatapultSpawns[i]; - var definition = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == spawn.Number); - if (definition is null) - { - continue; - } - - var spawnArea = new MonsterSpawnArea - { - MonsterDefinition = definition, - Quantity = 1, - X1 = spawn.X, - X2 = spawn.X, - Y1 = spawn.Y, - Y2 = spawn.Y, - Direction = Direction.South, - SpawnTrigger = SpawnTrigger.OnceAtEventStart, - }; - - await concrete.MapInitializer.InitializeSpawnAsync(2000 + i, map, spawnArea).ConfigureAwait(false); + await concrete.MapInitializer + .InitializeSpawnAsync(2000 + index++, map, CreateSpawnArea(npc)) + .ConfigureAwait(false); } } catch (Exception ex) @@ -356,27 +373,53 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } } + private static bool IsCatapult(CastleSiegeNpcDefinition npc) + => npc.MonsterDefinition is { } monster + && (monster.Number == CatapultAttackNumber || monster.Number == CatapultDefenseNumber); + + private static MonsterSpawnArea CreateSpawnArea(CastleSiegeNpcDefinition npc) => new() + { + MonsterDefinition = npc.MonsterDefinition, + Quantity = 1, + X1 = npc.SpawnX, + X2 = npc.SpawnX, + Y1 = npc.SpawnY, + Y2 = npc.SpawnY, + Direction = npc.Direction, + SpawnTrigger = SpawnTrigger.OnceAtEventStart, + }; + + private static Point? GetNpcPosition(IGameContext gameContext, short monsterNumber) + { + var npc = GetDefinition(gameContext)?.NpcDefinitions + .FirstOrDefault(n => n.MonsterDefinition?.Number == monsterNumber); + return npc is null ? null : new Point(npc.SpawnX, npc.SpawnY); + } + private static async Task ProcessSiegeTickAsync(IGameContext gameContext, CastleSiegeContext context) { try { - var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); - if (map is null) + if (await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map) { return; } // Each Crown Switch is held by whichever registered guild currently has a member standing on it. - foreach (var (switchNumber, x, y) in SwitchPositions) + foreach (var switchNumber in CastleSiegeContext.SwitchNumbers) { - string? holder = null; - var nearby = map.GetAttackablesInRange(new Point(x, y), SwitchHoldRange).OfType(); - foreach (var player in nearby) + if (GetNpcPosition(gameContext, switchNumber) is not { } position) { - var guildName = await GetGuildNameAsync(player).ConfigureAwait(false); - if (guildName is not null && context.RegisteredGuilds.Contains(guildName)) + continue; + } + + Guid? holder = null; + foreach (var player in map.GetAttackablesInRange(position, SwitchHoldRange).OfType()) + { + if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild + && context.IsRegistered(guild.Id)) { - holder = guildName; + holder = guild.Id; break; } } @@ -386,24 +429,31 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom var now = DateTime.UtcNow; - // Crown-hold capture: when a guild holds both switches with every gate down, the crown shield drops; - // that guild's master then holds the crown for CrownHoldDuration to take the throne (contestable). + // Crown-hold capture: when a guild holds both switches with every defense down, the crown shield + // drops; that guild's master then holds the crown for the configured time to take the throne. var eligible = context.GetShieldEligibleGuild(); Player? masterPlayer = null; - if (eligible is not null) + string? eligibleName = null; + if (eligible is { } eligibleId && GetNpcPosition(gameContext, CrownNumber) is { } crownPosition) { - foreach (var player in map.GetAttackablesInRange(CrownPosition, CrownHoldRange).OfType()) + foreach (var player in map.GetAttackablesInRange(crownPosition, CrownHoldRange).OfType()) { - if (player.GuildStatus?.Position == GuildPosition.GuildMaster - && await GetGuildNameAsync(player).ConfigureAwait(false) == eligible) + if (player.GuildStatus?.Position != GuildPosition.GuildMaster) + { + continue; + } + + if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild && guild.Id == eligibleId) { masterPlayer = player; + eligibleName = guild.Name; break; } } } - var crown = context.TickCrownHold(eligible, masterPlayer is not null, now, context.Configuration.CrownHoldDuration); + var holdDuration = TimeSpan.FromSeconds(GetDefinition(gameContext)?.CrownHoldTimeSeconds ?? 60); + var crown = context.TickCrownHold(eligible, eligibleName, masterPlayer is not null, now, holdDuration); // Shield drop/raise -> everyone on the battle map, but only when it flips (the packet pops a modal). if (crown.ShieldChanged) @@ -414,13 +464,13 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom switch (crown.Event) { case CrownEvent.HoldStarted when masterPlayer is not null: - // The 60-second registration panel is shown ONLY to the master taking the crown. + // The registration panel is shown ONLY to the master taking the crown. await masterPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false); break; case CrownEvent.HoldReset when masterPlayer is not null: await masterPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false); break; - case CrownEvent.Captured when crown.Guild is { } captured: + case CrownEvent.Captured when crown.GuildName is { } captured: await ForEachOnBattleMapAsync(gameContext, p => p.AnnounceSealCapturedAsync(captured)).ConfigureAwait(false); await AnnounceAsync(gameContext, $"Guild '{captured}' has taken the Crown and now holds the throne!").ConfigureAwait(false); break; @@ -430,7 +480,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom // Keep the client's on-map countdown armed and in sync. Resend every 10s so players who just // loaded the battle map pick it up, without visibly resetting the second-counter too often. - if ((int)(now - context.PhaseStartedUtc).TotalSeconds % 10 == 0) + if ((int)(now - context.StateStartedUtc).TotalSeconds % 10 == 0) { var remaining = context.GetRemainingSiegeTime(now); var totalMinutes = (int)Math.Ceiling(remaining.TotalMinutes); @@ -470,16 +520,131 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom ? player.InvokeViewPlugInAsync(action).AsTask() : Task.CompletedTask); - private async ValueTask BroadcastCastleFlagAsync(IGameContext gameContext, CastleSiegeContext context) + private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context) { try { - if (context.OwnerGuildName is not { Length: > 0 } owner) + var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); + if (map?.SafeZoneSpawnGate is not { } gate) { return; } - var logo = await this.GetOwnerLogoAsync(gameContext, owner).ConfigureAwait(false); + await gameContext.ForEachPlayerAsync(async player => + { + if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild + && context.IsRegistered(guild.Id)) + { + await player.WarpToAsync(gate).ConfigureAwait(false); + } + }).ConfigureAwait(false); + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: error while warping registered members to the battle map."); + } + } + + /// + /// Writes the castle owner and the guild registrations to their database tables, and the cycle + /// bookkeeping (current state and when it started) to the plugin's custom-configuration JSON. + /// + private async ValueTask PersistStateAsync(GameContext gameContext, CastleSiegeContext context) + { + try + { + await this.PersistOwnerAsync(gameContext, context).ConfigureAwait(false); + await PersistRegistrationsAsync(gameContext, context).ConfigureAwait(false); + await this.PersistCycleBookkeepingAsync(gameContext, context).ConfigureAwait(false); + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: error while persisting state to the database."); + } + } + + private async ValueTask PersistOwnerAsync(GameContext gameContext, CastleSiegeContext context) + { + using var dataContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeData), false, gameContext.Configuration); + var data = (await dataContext.GetAsync().ConfigureAwait(false)).FirstOrDefault() + ?? dataContext.CreateNew(); + + data.OwnerGuildId = context.OwnerGuildId; + data.IsOccupied = context.OwnerGuildId is not null; + await dataContext.SaveChangesAsync().ConfigureAwait(false); + + gameContext.LoggerFactory.CreateLogger() + .LogInformation( + "Castle Siege: persisted owner={owner} state={state}.", + context.OwnerGuildName ?? "(none)", + context.State); + } + + private static async ValueTask PersistRegistrationsAsync(GameContext gameContext, CastleSiegeContext context) + { + using var registrationContext = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(CastleSiegeGuildRegistration), false, gameContext.Configuration); + var existing = (await registrationContext.GetAsync().ConfigureAwait(false)).ToList(); + var wanted = context.RegisteredGuildIds.ToHashSet(); + + foreach (var registration in existing) + { + if (!wanted.Remove(registration.GuildId)) + { + await registrationContext.DeleteAsync(registration).ConfigureAwait(false); + } + } + + foreach (var guildId in wanted) + { + var registration = registrationContext.CreateNew(); + registration.GuildId = guildId; + } + + await registrationContext.SaveChangesAsync().ConfigureAwait(false); + } + + private async ValueTask PersistCycleBookkeepingAsync(GameContext gameContext, CastleSiegeContext context) + { + if (this.Configuration is not { } settings) + { + return; + } + + settings.PersistedState = context.State; + settings.PersistedStateStartedUtc = context.StateStartedUtc; + + // Find our plugin-configuration row via the in-memory config graph to get its id, then rewrite its + // custom-configuration JSON in its own (non-caching) context. + var pluginTypeId = typeof(CastleSiegeEventPlugIn).GUID; + var inMemory = gameContext.Configuration.PlugInConfigurations.FirstOrDefault(c => c.TypeId == pluginTypeId); + if (inMemory is null) + { + return; + } + + using var ctx = gameContext.PersistenceContextProvider.CreateNewTypedContext(typeof(PlugInConfiguration), false, gameContext.Configuration); + var row = await ctx.GetByIdAsync(inMemory.GetId()).ConfigureAwait(false); + if (row is null) + { + return; + } + + row.SetConfiguration(settings, gameContext.PlugInManager.CustomConfigReferenceHandler); + await ctx.SaveChangesAsync().ConfigureAwait(false); + } + + private async ValueTask BroadcastCastleFlagAsync(IGameContext gameContext, CastleSiegeContext context) + { + try + { + if (context.OwnerGuildId is not { } owner) + { + return; + } + + var logo = await this.GetOwnerLogoAsync(gameContext, owner, context.OwnerGuildName).ConfigureAwait(false); if (logo is null) { return; @@ -500,77 +665,32 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } } - private async ValueTask GetOwnerLogoAsync(IGameContext gameContext, string ownerName) + private async ValueTask GetOwnerLogoAsync(IGameContext gameContext, Guid ownerId, string? ownerName) { - if (this._cachedFlagOwner == ownerName && this._cachedFlagLogo is not null) + if (this._cachedFlagOwner == ownerId && this._cachedFlagLogo is not null) { return this._cachedFlagLogo; } - if (gameContext is not IGameServerContext serverContext) + if (gameContext is not IGameServerContext serverContext || ownerName is not { Length: > 0 }) { return null; } - var guildId = await serverContext.GuildServer.GetGuildIdByNameAsync(ownerName).ConfigureAwait(false); - if (guildId == 0) + var shortGuildId = await serverContext.GuildServer.GetGuildIdByNameAsync(ownerName).ConfigureAwait(false); + if (shortGuildId == 0) { return null; } - var guild = await serverContext.GuildServer.GetGuildAsync(guildId).ConfigureAwait(false); + var guild = await serverContext.GuildServer.GetGuildAsync(shortGuildId).ConfigureAwait(false); if (guild?.Logo is not { Length: > 0 } logo) { return null; } - this._cachedFlagOwner = ownerName; + this._cachedFlagOwner = ownerId; this._cachedFlagLogo = logo; return logo; } - - private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context) - { - try - { - var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); - if (map?.SafeZoneSpawnGate is not { } gate) - { - return; - } - - await gameContext.ForEachPlayerAsync(async player => - { - var guildName = await GetGuildNameAsync(player).ConfigureAwait(false); - if (guildName is not null && context.RegisteredGuilds.Contains(guildName)) - { - await player.WarpToAsync(gate).ConfigureAwait(false); - } - }).ConfigureAwait(false); - } - catch (Exception ex) - { - gameContext.LoggerFactory.CreateLogger() - .LogError(ex, "Castle Siege: error while warping registered members to the battle map."); - } - } - - private static async ValueTask GetGuildNameAsync(Player player) - { - if (player.GuildStatus is not { } guildStatus) - { - return null; - } - - if (player.GameContext is IGameServerContext serverContext) - { - var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); - if (guild?.Name is { Length: > 0 } name) - { - return name; - } - } - - return guildStatus.GuildId.ToString(); - } } From af46499279a96cb489719aff6b641342235ecf56 Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 4 Aug 2026 21:43:39 +0300 Subject: [PATCH 3/4] fix(build): run the persistence generator against the current data model The PreBuild targets ran the generator with "--no-build", so it used whatever assemblies happened to sit in its output folder. When that copy of the data model was older than a newly added type, the generator regenerated the checked-in *.Generated.cs files WITHOUT that type and overwrote them in the source tree. Nothing failed at build time: the C# compile stayed green and docker builds (-p:ci=true) skip the generator entirely, so they compiled whatever was in the tree. The damage only surfaced at runtime, when EF validated the model and found the inherited GameConfiguration.CastleSiegeConfiguration navigation pointing at a keyless type - the server died on startup with "The entity type 'CastleSiegeConfiguration' requires a primary key to be defined". Dropping the switch makes the generator build first, so its output always matches the data model. The regenerated files here are that missing output: the Castle Siege mappings, and the packet tests for packets whose XML was already committed. TypedContextModelTests builds the typed context the startup reads its plugin configurations through - the first one to touch the model - so this class of breakage fails in seconds without a database. Co-Authored-By: Claude Opus 5 (1M context) --- .../BasicModel/GameConfiguration.Generated.cs | 18 ++++ ....OpenMU.Persistence.EntityFramework.csproj | 9 +- .../Model/ExtendedTypeContext.Generated.cs | 21 ++++ .../Model/GameConfiguration.Generated.cs | 26 +++++ .../Model/MapsterConfigurator.Generated.cs | 24 +++++ .../MUnique.OpenMU.Persistence.csproj | 9 +- .../ClientToServerPacketTests.cs | 18 ++++ .../ServerToClientPacketTests.cs | 96 ++++++++++++++++++- .../TypedContextModelTests.cs | 56 +++++++++++ 9 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs 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; + } + } + } +} From f8e856c7c6f066a5cb0b325fa297b0839920007b Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Tue, 4 Aug 2026 21:44:12 +0300 Subject: [PATCH 4/4] feat(castle-siege): operate the Crown Switches by clicking, capture the crown by holding it The switches used to be held by simply standing near them, and the crown captured by standing near it - clicking a switch only produced the client's "not implemented yet" message. This drives both from the original interaction instead: - Clicking a Crown Switch starts an operation which completes after CastleSiegeSettings.SwitchPushSeconds (15) and keeps the switch for the guild until its operator leaves the switch's area. One player per switch; anybody else clicking it is told another team is on it (C1 B2 14 state 2). - While one guild holds both switches the crown's shield drops for it, and its guild master captures the throne by CLICKING the crown and holding it for CrownHoldTimeSeconds - seeded to 60 now, to match the countdown the client's registration panel hardcodes. The throne stays contestable until the siege ends. - The shield now depends on the switches alone, as in the original; the gates and statues remain what they always were, the obstacle in the way. The switch info packet (C1 B2 20) is broadcast before any switch-state packet because the client's "switch released" handler reads its switch table without checking that it exists - that table is only allocated when the info packet arrives, so the wrong order crashes the client. Also fixed while in here: - The crown registration panel could never be closed: the cancel was only sent while the master still stood on the crown, which is precisely when the hold does NOT break. The panel is now closed for the player it was opened for. - A contested switch was decided by enumeration order. - Panels opened by the siege are closed when it ends. - TryCaptureThrone was dead code carrying a second, diverged rule set. - /csphase advertised the pre-refactor phase names to the client. - The periodic broadcasts keyed off "UtcNow.Second % n", which silently skips when a tick runs late; they count ticks now. The unit tests never compiled against the refactored model - they are migrated to the state machine and guild ids, and cover the new switch and crown rules. A new test proves update 105 writes the configuration into an existing database. ApplyPendingUpdatesTool applies pending configuration updates without the admin panel; it is [Explicit], so it never runs in a normal test pass. Co-Authored-By: Claude Opus 5 (1M context) --- .../Configuration/MonsterDefinition.cs | 10 + .../CastleSiege/CastleSiegeContext.cs | 175 ++++++---- .../CastleSiege/CastleSiegeSettings.cs | 6 + .../CastleSiege/CastleSiegeSwitchEvent.cs | 20 ++ .../CastleSiege/CastleSiegeSwitchOperation.cs | 55 ++++ .../CastleSiege/CastleSiegeSwitchPush.cs | 23 ++ .../CastleSiegeSwitchTalkPlugIn.cs | 92 ++++++ .../CastleSiegeThroneCaptureTalkPlugIn.cs | 36 ++- .../CastleSiegePhaseChatCommandPlugIn.cs | 2 +- .../PeriodicTasks/CastleSiegeEventPlugIn.cs | 184 +++++++++-- .../ICastleSiegeStatusViewPlugIn.cs | 25 ++ .../CastleSiegeStatusViewPlugIn.cs | 70 ++++ .../Events/CastleSiegeInitializer.cs | 2 +- .../ApplyPendingUpdatesTool.cs | 58 ++++ .../TestInitializationWithEfCore.cs | 43 +++ .../CastleSiege/CastleSiegeContextTest.cs | 298 +++++++++++------- 16 files changed, 893 insertions(+), 206 deletions(-) create mode 100644 src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs create mode 100644 src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs create mode 100644 src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs create mode 100644 src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs create mode 100644 tests/MUnique.OpenMU.Persistence.Initialization.Tests/ApplyPendingUpdatesTool.cs diff --git a/src/DataModel/Configuration/MonsterDefinition.cs b/src/DataModel/Configuration/MonsterDefinition.cs index 59a1d7c..6263e55 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/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs index 1361fb9..67eec74 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs @@ -27,10 +27,12 @@ using MUnique.OpenMU.DataModel.Configuration; /// re-create under the same name) therefore can no longer transfer castle ownership to the wrong guild. Names /// are carried alongside purely for display and for the packets that send a name to the client. /// -/// Battle rule: attackers must destroy all castle defenses (gates + guardian statues) and then hold BOTH -/// Crown Switches at the same time — the switches are held by standing on them (evaluated per tick by the -/// plugin), and once both are held by one guild with the defenses down, that guild captures the throne. -/// The throne holder when the siege ends becomes the castle owner. +/// Battle rule: a guild takes the throne by holding BOTH Crown Switches at the same time. A switch is +/// operated by clicking it and then staying in its area: the operation needs +/// to complete, after which the switch counts as held +/// until its operator leaves. While one guild holds both switches the crown's shield drops for it, and its +/// guild master can start the crown hold to capture the throne. The throne can change hands as often as the +/// switches do; whoever holds it when the siege ends becomes the castle owner. /// public class CastleSiegeContext { @@ -38,7 +40,7 @@ public class CastleSiegeContext public static readonly short[] SwitchNumbers = { 217, 218 }; private readonly Dictionary _registeredGuilds = new(); - private readonly Dictionary _switchHolders = new() { { 217, null }, { 218, null } }; + private readonly Dictionary _switches = new() { { 217, null }, { 218, null } }; private DateTime _stateStartedUtc; private Guid? _occupier; private string? _occupierName; @@ -46,6 +48,7 @@ public class CastleSiegeContext private bool _dirty; private Guid? _crownHoldGuild; private DateTime? _crownHoldStartUtc; + private Guid? _crownHoldRequestedBy; private bool _lastShieldDown; /// Initializes a new instance of the class. @@ -270,27 +273,46 @@ public class CastleSiegeContext } /// - /// Returns the guild that currently holds BOTH crown switches while all castle defenses are down (so the - /// crown's shield is dropped for them), or null. Only meaningful during the siege. + /// Returns the guild which currently holds BOTH Crown Switches, so the crown's shield is dropped for it, + /// or null. Only meaningful during the siege. /// public Guid? GetShieldEligibleGuild() { - if (!this.IsSiegeRunning || this._defensesRemaining > 0) + if (!this.IsSiegeRunning) { return null; } - var holder = this._switchHolders[217]; - return holder is not null && holder == this._switchHolders[218] ? holder : null; + var first = this.GetHeldSwitchGuild(217); + return first is not null && first == this.GetHeldSwitchGuild(218) ? first : null; } /// - /// Advances the crown-hold capture. is the guild with both switches held - /// and no defenses left (shield down); is whether that guild's master is - /// standing on the crown. Captures the throne for the guild once it has held for . - /// Losing a switch or the master leaving the crown resets the hold (contestable until the siege ends). + /// Registers a guild master's intent to take the crown, which is what the crown hold waits for: standing + /// on the crown alone does nothing until its guild master clicked it. Ignored when the guild does not + /// hold both switches, so a click can never arm a hold the guild isn't entitled to. /// - /// The guild with both switches and no defenses, or null. + /// The requesting guild master's guild identifier. + /// if the request was accepted. + public bool RequestCrownHold(Guid guildId) + { + if (this.GetShieldEligibleGuild() != guildId || this._occupier == guildId) + { + return false; + } + + this._crownHoldRequestedBy = guildId; + return true; + } + + /// + /// Advances the crown-hold capture. is the guild holding both switches + /// (shield down) and is whether that guild's master stands on the crown. + /// The hold only runs after the master requested it via ; it captures the + /// throne once it ran for . Losing a switch or the master leaving the crown + /// resets the hold, and the crown has to be clicked again (contestable until the siege ends). + /// + /// The guild holding both switches, or null. /// That guild's name, for display. /// Whether that guild's master is on the crown. /// The current UTC time. @@ -303,21 +325,21 @@ public class CastleSiegeContext if (!this.IsSiegeRunning) { - this._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this.ResetCrownHold(); return new CrownTickResult(false, shieldChanged, CrownEvent.None, null, null); } var wasHolding = this._crownHoldGuild is not null; - // The guild that already occupies the throne just holds it — no re-registration (avoids a capture loop). + // The guild that already occupies the throne just holds it - no re-registration (avoids a capture loop). // Only a DIFFERENT guild taking both switches can register/capture (contest). - var canCapture = eligibleGuild is not null && eligibleGuild != this._occupier; + var canCapture = eligibleGuild is not null + && eligibleGuild != this._occupier + && this._crownHoldRequestedBy == eligibleGuild; if (!canCapture || !masterHolding) { - this._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this.ResetCrownHold(); return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null, null); } @@ -332,8 +354,7 @@ public class CastleSiegeContext { this._occupier = eligibleGuild; this._occupierName = eligibleGuildName; - this._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this.ResetCrownHold(); this._dirty = true; return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild, eligibleGuildName); } @@ -392,56 +413,83 @@ public class CastleSiegeContext } } + /// Returns who is currently operating a Crown Switch, or . + /// The Crown Switch NPC number (217 or 218). + public CastleSiegeSwitchOperation? GetSwitchOperation(short switchNumber) + => this._switches.TryGetValue(switchNumber, out var operation) ? operation : null; + /// - /// Sets which guild is currently standing on (holding) a Crown Switch. Called every tick by the plugin - /// based on player positions. Pass null when no registered member stands on it. No-op outside the siege. + /// Starts operating a Crown Switch for a player who clicked it. A switch can only be operated by one + /// player at a time: while somebody else is on it, the click is refused and the caller is told who holds + /// it, which is what the client shows as "another siege team is running the crown switch". /// /// The Crown Switch NPC number (217 or 218). - /// The holding guild's persistent identifier, or null. - public void SetSwitchHolder(short switchNumber, Guid? guildId) + /// The clicking player's guild identifier. + /// The clicking player's guild name, for display. + /// The clicking player's object identifier on the map. + /// The clicking player's name, for display. + /// The switch NPC's object identifier on the map. + /// The current UTC time. + /// The outcome, and the current operation when the switch is taken. + public (CastleSiegeSwitchPush Result, CastleSiegeSwitchOperation? Operation) TryStartSwitchOperation( + short switchNumber, + Guid guildId, + string guildName, + ushort playerId, + string playerName, + ushort switchObjectId, + DateTime now) { - if (this.IsSiegeRunning && this._switchHolders.ContainsKey(switchNumber)) + if (!this.IsSiegeRunning || !this._switches.ContainsKey(switchNumber)) { - this._switchHolders[switchNumber] = guildId; + return (CastleSiegeSwitchPush.SiegeNotRunning, null); } + + if (this._switches[switchNumber] is { } current) + { + return current.PlayerId == playerId + ? (CastleSiegeSwitchPush.AlreadyYours, current) + : (CastleSiegeSwitchPush.TakenByOther, current); + } + + var operation = new CastleSiegeSwitchOperation(guildId, guildName, playerId, playerName, switchObjectId, now); + this._switches[switchNumber] = operation; + return (CastleSiegeSwitchPush.Started, operation); } /// - /// Attempts to capture the throne for a guild (called when a member registers at the Sinior/Crown NPC). - /// Succeeds only during the siege when the throne is free, all castle defenses are destroyed, and the - /// guild is currently holding BOTH Crown Switches (a member standing on each). + /// Advances one Crown Switch. The operation is dropped as soon as its player is gone from the switch's + /// area, and completes - which makes the switch count for the guild - once it ran . /// - /// The capturing guild's persistent identifier. - /// The capturing guild's name, for display. - /// Whether it succeeded and a human-readable reason/result message. - public (bool Success, string Reason) TryCaptureThrone(Guid guildId, string guildName) + /// The Crown Switch NPC number (217 or 218). + /// Whether the operating player is still in the switch's area. + /// The current UTC time. + /// How long operating the switch takes. + /// What happened to the switch in this tick, and the operation it happened to. + public (CastleSiegeSwitchEvent Event, CastleSiegeSwitchOperation? Operation) TickSwitch( + short switchNumber, + bool operatorPresent, + DateTime now, + TimeSpan pushDuration) { - if (!this.IsSiegeRunning) + if (!this._switches.TryGetValue(switchNumber, out var operation) || operation is null) { - return (false, "The siege is not running."); + return (CastleSiegeSwitchEvent.None, null); } - if (this._occupier is { } occupier) + if (!this.IsSiegeRunning || !operatorPresent) { - return (false, occupier == guildId - ? "Your guild already holds the throne." - : $"The throne is already held by '{this._occupierName ?? occupier.ToString()}'."); + this._switches[switchNumber] = null; + return (CastleSiegeSwitchEvent.Released, operation); } - if (this._defensesRemaining > 0) + if (!operation.IsHeld && now - operation.StartedUtc >= pushDuration) { - return (false, $"Destroy the castle defenses first ({this._defensesRemaining} remaining)."); + operation.MarkHeld(); + return (CastleSiegeSwitchEvent.Held, operation); } - if (this._switchHolders[217] != guildId || this._switchHolders[218] != guildId) - { - return (false, "Your guild must be holding BOTH Crown Switches at once (stand a member on each)."); - } - - this._occupier = guildId; - this._occupierName = guildName; - this._dirty = true; - return (true, "throne captured"); + return (CastleSiegeSwitchEvent.None, operation); } /// Returns a human-readable status summary for admin display. @@ -451,20 +499,29 @@ public class CastleSiegeContext + $"defenses={this._defensesRemaining}, throne={this._occupierName ?? "(none)"}, " + $"switch217={this.DescribeSwitch(217)}, switch218={this.DescribeSwitch(218)}"; + private Guid? GetHeldSwitchGuild(short switchNumber) + => this._switches[switchNumber] is { IsHeld: true } operation ? operation.GuildId : null; + private string DescribeSwitch(short switchNumber) - => this._switchHolders[switchNumber] is { } holder - ? (this._registeredGuilds.TryGetValue(holder, out var name) ? name : holder.ToString()) + => this._switches[switchNumber] is { } operation + ? $"{operation.GuildName}/{operation.PlayerName}{(operation.IsHeld ? string.Empty : " (pushing)")}" : "-"; + private void ResetCrownHold() + { + this._crownHoldGuild = null; + this._crownHoldStartUtc = null; + this._crownHoldRequestedBy = null; + } + private void ClearBattleState() { - this._switchHolders[217] = null; - this._switchHolders[218] = null; + this._switches[217] = null; + this._switches[218] = null; this._defensesRemaining = 0; this._occupier = null; this._occupierName = null; - this._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this.ResetCrownHold(); this._lastShieldDown = false; } diff --git a/src/GameLogic/CastleSiege/CastleSiegeSettings.cs b/src/GameLogic/CastleSiege/CastleSiegeSettings.cs index a7bd6f4..5e98ca9 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeSettings.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeSettings.cs @@ -117,6 +117,12 @@ public class CastleSiegeSettings [Browsable(false)] public TimeSpan SiegeDuration { get; set; } = TimeSpan.FromMinutes(10); + /// + /// Gets or sets how many seconds a player has to operate a Crown Switch before it counts for their guild. + /// The player has to stay in the switch's area for that long, and keeps it until they leave. + /// + public int SwitchPushSeconds { get; set; } = 15; + // --- Persisted cycle bookkeeping (hidden from the AdminPanel) --- // Only the CURRENT state and when it started ride on the plugin's custom-configuration JSON. The castle // owner and the guild registrations live in real database tables, so they are not duplicated here. diff --git a/src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs b/src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs new file mode 100644 index 0000000..d0e14fa --- /dev/null +++ b/src/GameLogic/CastleSiege/CastleSiegeSwitchEvent.cs @@ -0,0 +1,20 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.CastleSiege; + +/// +/// What happened to a Crown Switch during one tick. +/// +public enum CastleSiegeSwitchEvent +{ + /// Nothing worth reporting. + None, + + /// The operation completed, so the switch now counts for the operator's guild. + Held, + + /// The operator left (or the siege ended), so the switch is free again. + Released, +} diff --git a/src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs b/src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs new file mode 100644 index 0000000..4c41fa0 --- /dev/null +++ b/src/GameLogic/CastleSiege/CastleSiegeSwitchOperation.cs @@ -0,0 +1,55 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.CastleSiege; + +/// +/// One player operating one Crown Switch. The player starts it by clicking the switch and keeps it by +/// staying in its area; the switch counts as held for the guild once the operation has run its time. +/// +public class CastleSiegeSwitchOperation +{ + /// Initializes a new instance of the class. + /// The operating player's guild identifier. + /// The operating player's guild name, for display. + /// The operating player's object identifier on the map. + /// The operating player's name, for display. + /// The switch NPC's object identifier on the map. + /// When the operation started (UTC). + public CastleSiegeSwitchOperation(Guid guildId, string guildName, ushort playerId, string playerName, ushort switchObjectId, DateTime startedUtc) + { + this.GuildId = guildId; + this.GuildName = guildName; + this.PlayerId = playerId; + this.PlayerName = playerName; + this.SwitchObjectId = switchObjectId; + this.StartedUtc = startedUtc; + } + + /// Gets the operating player's guild identifier. + public Guid GuildId { get; } + + /// Gets the operating player's guild name. + public string GuildName { get; } + + /// Gets the operating player's object identifier on the map. + public ushort PlayerId { get; } + + /// Gets the operating player's name. + public string PlayerName { get; } + + /// Gets the switch NPC's object identifier on the map, which the client's packets refer to. + public ushort SwitchObjectId { get; } + + /// Gets the point in time (UTC) when the operation started. + public DateTime StartedUtc { get; } + + /// + /// Gets a value indicating whether the operation ran its time, so the switch counts for the guild. + /// + public bool IsHeld { get; private set; } + + /// Marks the operation as completed, which makes the switch count for the guild. + internal void MarkHeld() => this.IsHeld = true; +} diff --git a/src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs b/src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs new file mode 100644 index 0000000..65353a2 --- /dev/null +++ b/src/GameLogic/CastleSiege/CastleSiegeSwitchPush.cs @@ -0,0 +1,23 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.CastleSiege; + +/// +/// The outcome of a player clicking a Crown Switch. +/// +public enum CastleSiegeSwitchPush +{ + /// The player started operating the switch. + Started, + + /// The player is already operating this switch. + AlreadyYours, + + /// Somebody else is operating this switch. + TakenByOther, + + /// The siege is not running, so the switches do nothing. + SiegeNotRunning, +} diff --git a/src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs new file mode 100644 index 0000000..25f772d --- /dev/null +++ b/src/GameLogic/CastleSiege/CastleSiegeSwitchTalkPlugIn.cs @@ -0,0 +1,92 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.CastleSiege; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.GameLogic.PlugIns; +using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; +using MUnique.OpenMU.GameLogic.Views; +using MUnique.OpenMU.GameLogic.Views.CastleSiege; +using MUnique.OpenMU.Interfaces; +using MUnique.OpenMU.PlugIns; + +/// +/// Handles clicking a Crown Switch (NPC 217 / 218) on Valley of Loren. The click starts operating the +/// switch, which the client shows as a progress box; the switch counts for the guild once the operation +/// ran its time and stays theirs until the operating player leaves the switch's area. Only one player can +/// operate a switch at a time - anybody else clicking it is told that another team is on it. +/// +[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000217")] +[PlugIn] +[Display(Name = "Castle Siege Crown Switch", Description = "Operates a Crown Switch (NPC 217/218) during the Castle Siege.")] +public class CastleSiegeSwitchTalkPlugIn : IPlayerTalkToNpcPlugIn +{ + /// + public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs) + { + if (!CastleSiegeContext.SwitchNumbers.Contains(npc.Definition.Number)) + { + return; + } + + // We drive the switch ourselves, so suppress the default "not implemented" message. + eventArgs.HasBeenHandled = true; + + var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext); + if (context is null) + { + await ShowAsync(player, "Castle Siege is not active on this server.").ConfigureAwait(false); + return; + } + + if (!context.IsSiegeRunning) + { + await ShowAsync(player, "The Crown Switches only work while the siege is running.").ConfigureAwait(false); + return; + } + + if (await CastleSiegeEventPlugIn.GetPersistentGuildAsync(player).ConfigureAwait(false) is not { } guild + || !context.IsRegistered(guild.Id)) + { + await ShowAsync(player, "Only members of a registered guild can operate the Crown Switches.").ConfigureAwait(false); + return; + } + + var (result, operation) = context.TryStartSwitchOperation( + npc.Definition.Number, + guild.Id, + guild.Name, + player.Id, + player.Name, + npc.Id, + DateTime.UtcNow); + + switch (result) + { + case CastleSiegeSwitchPush.Started: + // The info packet goes first: it is what makes every client allocate its switch table, which + // the "switch released" packet later reads without checking that it exists. + await CastleSiegeEventPlugIn.BroadcastSwitchInfoAsync(player.GameContext, npc.Id, operation).ConfigureAwait(false); + await player.InvokeViewPlugInAsync(p => p.SetCrownSwitchStateAsync(npc.Id, player.Id, 1)).ConfigureAwait(false); + break; + + case CastleSiegeSwitchPush.TakenByOther when operation is { } other: + // State 2 makes the client name the player who is already on it. + await player.InvokeViewPlugInAsync(p => p.SetCrownSwitchStateAsync(npc.Id, other.PlayerId, 2)).ConfigureAwait(false); + break; + + case CastleSiegeSwitchPush.AlreadyYours: + break; + + default: + await ShowAsync(player, "The Crown Switches only work while the siege is running.").ConfigureAwait(false); + break; + } + } + + private static ValueTask ShowAsync(Player player, string text) + => player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(text, MessageType.BlueNormal)); +} diff --git a/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs index b6fca0b..5ea1a42 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs @@ -55,34 +55,46 @@ public class CastleSiegeThroneCaptureTalkPlugIn : IPlayerTalkToNpcPlugIn return; } - // The throne is taken by holding the Crown, not by talking here — give guidance based on the state. - await ShowAsync(player, DescribeThroneStep(context, guild.Id)).ConfigureAwait(false); + // Clicking the Crown as the guild master is what arms the capture: the hold then runs while they + // stay on it. Anybody else (or a master who isn't entitled yet) just gets told what is missing. + var isGuildMaster = player.GuildStatus?.Position == GuildPosition.GuildMaster; + if (isGuildMaster && context.RequestCrownHold(guild.Id)) + { + await ShowAsync(player, "Hold the Crown - do not step away until the seal is registered!").ConfigureAwait(false); + return; + } + + await ShowAsync(player, DescribeThroneStep(context, guild.Id, isGuildMaster)).ConfigureAwait(false); } - private static string DescribeThroneStep(CastleSiegeContext context, Guid guildId) + private static string DescribeThroneStep(CastleSiegeContext context, Guid guildId, bool isGuildMaster) { if (!context.IsSiegeRunning) { return "The siege is not running yet."; } - if (context.DefensesRemaining > 0) - { - return $"Destroy all castle gates first ({context.DefensesRemaining} remaining), then hold both Crown Switches."; - } - var eligible = context.GetShieldEligibleGuild(); if (eligible is null) { - return "All gates are down! Hold BOTH Crown Switches with your guild — the Crown's shield will drop."; + return context.DefensesRemaining > 0 + ? $"Hold BOTH Crown Switches with your guild to drop the Crown's shield ({context.DefensesRemaining} castle defenses still standing)." + : "Hold BOTH Crown Switches with your guild - the Crown's shield will drop."; } - if (eligible == guildId) + if (eligible != guildId) { - return "Your guild holds both switches and the shield is down — send your GUILD MASTER to hold the Crown to take the throne!"; + return "Another guild is holding both switches. Take a switch back to raise their shield."; } - return "Another guild is holding both switches. Take a switch back to raise their shield."; + if (context.OccupierGuildId == guildId) + { + return "Your guild already holds the throne - keep it until the siege ends."; + } + + return isGuildMaster + ? "Your guild holds both switches, but the Crown cannot be registered right now." + : "Your guild holds both switches and the shield is down - your GUILD MASTER has to click the Crown!"; } private static ValueTask ShowAsync(Player player, string text) diff --git a/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs b/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs index 588fff7..b288ae6 100644 --- a/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs +++ b/src/GameLogic/PlugIns/ChatCommands/CastleSiegePhaseChatCommandPlugIn.cs @@ -14,7 +14,7 @@ using MUnique.OpenMU.PlugIns; /// Forces a specific Castle Siege phase. GM only. Usage: /csphase Siege. [Guid("A1B2C3D4-0003-4E5F-9A0B-CA5710000003")] [PlugIn] -[Display(Name = "Castle Siege Phase", Description = "GM command: /csphase ")] +[Display(Name = "Castle Siege Phase", Description = "GM command: /csphase ")] [ChatCommandHelp(Command, CharacterStatus.GameMaster)] public class CastleSiegePhaseChatCommandPlugIn : IChatCommandPlugIn { diff --git a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs index 486c222..731a37c 100644 --- a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs +++ b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs @@ -47,8 +47,27 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom private const int SwitchHoldRange = 3; private const int CrownHoldRange = 4; + /// How many ticks (the periodic task runs once per second) between two countdown broadcasts. + private const int SiegeStateBroadcastTicks = 10; + + /// How many ticks between two castle-flag broadcasts. + private const int CastleFlagBroadcastTicks = 15; + private static readonly ConcurrentDictionary Contexts = new(); + /// + /// The player whose client currently shows the crown registration panel, per game context. The panel is + /// opened for exactly one guild master, and it has to be closed for that same player - by the time the + /// hold breaks they are usually no longer on the crown, so they can't be found by position any more. + /// + private static readonly ConcurrentDictionary CrownHoldPlayers = new(); + + /// + /// Tick counters per game context, used to space out the periodic broadcasts. Counting ticks (instead of + /// matching a clock second) keeps a broadcast from being skipped when a tick runs late. + /// + private static readonly ConcurrentDictionary Counters = new(); + /// /// Maps the in-memory guild id (assigned by the guild server, not stable across restarts) to the guild's /// persistent identifier. Populated lazily; a miss costs one database lookup per guild per process. @@ -156,7 +175,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom // castle owner from the database so the hunting-map gate + castle flag rewards still work everywhere. if (!IsCastleSiegeServer(gameContext)) { - if (DateTime.UtcNow.Second % 15 == 0) + if (GetCounters(gameContext).NextCastleFlag()) { await LoadPersistedStateAsync(gameContext, context).ConfigureAwait(false); await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); @@ -180,7 +199,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } // Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any state). - if (DateTime.UtcNow.Second % 15 == 0) + if (GetCounters(gameContext).NextCastleFlag()) { await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); } @@ -281,8 +300,11 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false); break; case CastleSiegeState.End: - // Stop the on-map countdown for everyone still on the battle map. + // Stop the on-map countdown for everyone still on the battle map, and close the panels + // the siege opened - the battle state is dropped right after this, so whoever was + // operating a switch or holding the crown would keep a dead progress box on screen. await BroadcastSiegeStateAsync(gameContext, false, 0, 0).ConfigureAwait(false); + await CloseSiegePanelsAsync(gameContext, context).ConfigureAwait(false); break; case CastleSiegeState.Idle1 when context.OwnerGuildName is { } owner: await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false); @@ -389,6 +411,50 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom SpawnTrigger = SpawnTrigger.OnceAtEventStart, }; + private static BroadcastCounters GetCounters(IGameContext gameContext) + => Counters.GetOrAdd(gameContext, _ => new BroadcastCounters()); + + /// + /// Closes the client panels the siege opened: the crown registration panel of the master who was holding + /// it, and the switch progress box of whoever was operating a switch. Called when the siege ends, before + /// the battle state is dropped. + /// + private static async Task CloseSiegePanelsAsync(IGameContext gameContext, CastleSiegeContext context) + { + if (CrownHoldPlayers.TryRemove(gameContext, out var holdPlayer)) + { + await holdPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false); + } + + if (await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false) is not { } map) + { + return; + } + + foreach (var switchNumber in CastleSiegeContext.SwitchNumbers) + { + if (context.GetSwitchOperation(switchNumber) is { } operation) + { + await BroadcastSwitchInfoAsync(gameContext, operation.SwitchObjectId, null).ConfigureAwait(false); + await CloseSwitchBoxAsync(map, operation).ConfigureAwait(false); + } + } + } + + /// + /// Closes the switch progress box on the client of the player who was operating it. Object identifiers + /// are recycled when a player leaves, so the name is checked too - otherwise a newly connected player + /// could inherit the id and get a message box about a switch they never touched. + /// + private static async Task CloseSwitchBoxAsync(GameMap map, CastleSiegeSwitchOperation operation) + { + if (map.GetObject(operation.PlayerId) is Player player && player.Name == operation.PlayerName) + { + await player.InvokeViewPlugInAsync( + p => p.SetCrownSwitchStateAsync(operation.SwitchObjectId, operation.PlayerId, 0)).ConfigureAwait(false); + } + } + private static Point? GetNpcPosition(IGameContext gameContext, short monsterNumber) { var npc = GetDefinition(gameContext)?.NpcDefinitions @@ -405,32 +471,45 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom return; } - // Each Crown Switch is held by whichever registered guild currently has a member standing on it. + var now = DateTime.UtcNow; + var pushDuration = TimeSpan.FromSeconds(Math.Max(1, context.Configuration.SwitchPushSeconds)); + + // A Crown Switch belongs to the player who clicked it, for as long as they stay in its area. foreach (var switchNumber in CastleSiegeContext.SwitchNumbers) { - if (GetNpcPosition(gameContext, switchNumber) is not { } position) + if (context.GetSwitchOperation(switchNumber) is not { } operation) { continue; } - Guid? holder = null; - foreach (var player in map.GetAttackablesInRange(position, SwitchHoldRange).OfType()) + var stillOnIt = GetNpcPosition(gameContext, switchNumber) is { } position + && map.GetAttackablesInRange(position, SwitchHoldRange) + .OfType() + .Any(p => p.Id == operation.PlayerId && p.IsAlive); + + var (switchEvent, affected) = context.TickSwitch(switchNumber, stillOnIt, now, pushDuration); + if (affected is null) { - if (await GetPersistentGuildAsync(player).ConfigureAwait(false) is { } guild - && context.IsRegistered(guild.Id)) - { - holder = guild.Id; - break; - } + continue; } - context.SetSwitchHolder(switchNumber, holder); + if (switchEvent == CastleSiegeSwitchEvent.Held) + { + // Repeat the info so the HUD picks up the names (the client only stores them from the + // second packet on, because the first one allocates its table). + await BroadcastSwitchInfoAsync(gameContext, affected.SwitchObjectId, affected).ConfigureAwait(false); + } + else if (switchEvent == CastleSiegeSwitchEvent.Released) + { + await BroadcastSwitchInfoAsync(gameContext, affected.SwitchObjectId, null).ConfigureAwait(false); + + // Close the client's progress box of the player who left, if they are still around. + await CloseSwitchBoxAsync(map, affected).ConfigureAwait(false); + } } - var now = DateTime.UtcNow; - - // Crown-hold capture: when a guild holds both switches with every defense down, the crown shield - // drops; that guild's master then holds the crown for the configured time to take the throne. + // Crown-hold capture: while one guild holds both switches the crown shield drops for it, and its + // master captures the throne by clicking the crown and holding it for the configured time. var eligible = context.GetShieldEligibleGuild(); Player? masterPlayer = null; string? eligibleName = null; @@ -464,13 +543,25 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom switch (crown.Event) { case CrownEvent.HoldStarted when masterPlayer is not null: - // The registration panel is shown ONLY to the master taking the crown. + // The registration panel is shown ONLY to the master taking the crown. Remember them: + // the hold usually breaks BECAUSE they walked off the crown, and their client still has + // the panel open, so the cancel has to reach the player we started it for. + CrownHoldPlayers[gameContext] = masterPlayer; await masterPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false); break; - case CrownEvent.HoldReset when masterPlayer is not null: - await masterPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false); + case CrownEvent.HoldReset: + if (CrownHoldPlayers.TryRemove(gameContext, out var holdPlayer)) + { + await holdPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false); + } + break; case CrownEvent.Captured when crown.GuildName is { } captured: + if (CrownHoldPlayers.TryRemove(gameContext, out var capturingPlayer)) + { + await capturingPlayer.InvokeViewPlugInAsync(p => p.SetCrownRegistAsync(1, 0)).ConfigureAwait(false); + } + await ForEachOnBattleMapAsync(gameContext, p => p.AnnounceSealCapturedAsync(captured)).ConfigureAwait(false); await AnnounceAsync(gameContext, $"Guild '{captured}' has taken the Crown and now holds the throne!").ConfigureAwait(false); break; @@ -478,9 +569,10 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom break; } - // Keep the client's on-map countdown armed and in sync. Resend every 10s so players who just - // loaded the battle map pick it up, without visibly resetting the second-counter too often. - if ((int)(now - context.StateStartedUtc).TotalSeconds % 10 == 0) + // Keep the client's on-map countdown armed and in sync. Resend every 10 ticks so players who just + // loaded the battle map pick it up, without visibly resetting the second-counter too often. This + // counts ticks instead of matching a clock second, which a delayed tick would skip silently. + if (GetCounters(gameContext).NextSiegeState()) { var remaining = context.GetRemainingSiegeTime(now); var totalMinutes = (int)Math.Ceiling(remaining.TotalMinutes); @@ -514,6 +606,24 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom }); /// Invokes the Castle Siege status view for every player currently on the battle map. + /// + /// Tells everybody on the battle map who is operating a Crown Switch. Besides driving the client's HUD + /// list, this is what makes the client allocate its switch table, so it has to be sent before any + /// "switch released" packet reaches that client. + /// + /// The game context. + /// The switch NPC's object identifier. + /// The operation, or when the switch became free. + internal static ValueTask BroadcastSwitchInfoAsync(IGameContext gameContext, ushort switchObjectId, CastleSiegeSwitchOperation? operation) + => ForEachOnBattleMapAsync( + gameContext, + p => p.SetCrownSwitchInfoAsync( + switchObjectId, + operation is null ? (byte)0 : (byte)1, + (byte)CastleSiegeJoinSide.Attack1, + operation?.GuildName ?? string.Empty, + operation?.PlayerName ?? string.Empty)); + private static ValueTask ForEachOnBattleMapAsync(IGameContext gameContext, Func action) => gameContext.ForEachPlayerAsync(player => player.CurrentMap?.Definition.Number == ValleyOfLorenMapNumber @@ -693,4 +803,30 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom this._cachedFlagLogo = logo; return logo; } + + /// + /// Counts the ticks between the periodic broadcasts of one game context. + /// + private sealed class BroadcastCounters + { + private int _siegeState; + private int _castleFlag; + + /// Advances the countdown-broadcast counter and tells whether it is due. + public bool NextSiegeState() => Due(ref this._siegeState, SiegeStateBroadcastTicks); + + /// Advances the castle-flag-broadcast counter and tells whether it is due. + public bool NextCastleFlag() => Due(ref this._castleFlag, CastleFlagBroadcastTicks); + + private static bool Due(ref int counter, int period) + { + if (++counter < period) + { + return false; + } + + counter = 0; + return true; + } + } } diff --git a/src/GameLogic/Views/CastleSiege/ICastleSiegeStatusViewPlugIn.cs b/src/GameLogic/Views/CastleSiege/ICastleSiegeStatusViewPlugIn.cs index f99a79b..7050afe 100644 --- a/src/GameLogic/Views/CastleSiege/ICastleSiegeStatusViewPlugIn.cs +++ b/src/GameLogic/Views/CastleSiege/ICastleSiegeStatusViewPlugIn.cs @@ -49,4 +49,29 @@ public interface ICastleSiegeStatusViewPlugIn : IViewPlugIn /// /// The capturing guild's name (max 8 bytes). ValueTask AnnounceSealCapturedAsync(string guildName); + + /// + /// Sends the state of a Crown Switch (C1 B2 14): state 0 = released (the client closes its progress + /// box), 1 = this player is operating it (the client opens its hold progress box), 2 = somebody else + /// is already operating it. + /// + /// The Crown Switch NPC's object identifier. + /// The operating player's object identifier. + /// The switch state (0 released, 1 operated by this player, 2 operated by another). + ValueTask SetCrownSwitchStateAsync(ushort switchObjectId, ushort playerObjectId, byte state); + + /// + /// Sends who is operating a Crown Switch (C1 B2 20), which the client lists on the siege HUD. + /// + /// This has to reach a client BEFORE any with state 0: the client + /// allocates its switch table when this arrives, and its "switch released" handler reads that table + /// without checking whether it exists. + /// + /// + /// The Crown Switch NPC's object identifier. + /// 0 when nobody operates it, 1 while it is operated. + /// The operating side (see the castle siege join sides). + /// The operating guild's name (max 8 bytes), empty when free. + /// The operating player's name (max 10 bytes), empty when free. + ValueTask SetCrownSwitchInfoAsync(ushort switchObjectId, byte switchState, byte joinSide, string guildName, string playerName); } diff --git a/src/GameServer/RemoteView/CastleSiege/CastleSiegeStatusViewPlugIn.cs b/src/GameServer/RemoteView/CastleSiege/CastleSiegeStatusViewPlugIn.cs index 80b8e99..9cfc2d5 100644 --- a/src/GameServer/RemoteView/CastleSiege/CastleSiegeStatusViewPlugIn.cs +++ b/src/GameServer/RemoteView/CastleSiege/CastleSiegeStatusViewPlugIn.cs @@ -26,6 +26,17 @@ public class CastleSiegeStatusViewPlugIn : ICastleSiegeStatusViewPlugIn /// The player. public CastleSiegeStatusViewPlugIn(RemotePlayer player) => this._player = player; + private static void WriteName(string name, Span target) + { + if (name.Length == 0) + { + return; + } + + var bytes = System.Text.Encoding.UTF8.GetBytes(name); + bytes.AsSpan(0, Math.Min(target.Length - 1, bytes.Length)).CopyTo(target); + } + /// public async ValueTask SetBattleStateAsync(bool started) { @@ -153,6 +164,65 @@ public class CastleSiegeStatusViewPlugIn : ICastleSiegeStatusViewPlugIn await connection.SendAsync(WritePacket).ConfigureAwait(false); } + /// + public async ValueTask SetCrownSwitchStateAsync(ushort switchObjectId, ushort playerObjectId, byte state) + { + var connection = this._player.Connection; + if (connection is null) + { + return; + } + + int WritePacket() + { + // C1 09 B2 14 + var span = connection.Output.GetSpan(9)[..9]; + span.Clear(); + span[0] = 0xC1; + span[1] = 0x09; + span[2] = 0xB2; + span[3] = 0x14; + span[4] = (byte)(switchObjectId >> 8); + span[5] = (byte)switchObjectId; + span[6] = (byte)(playerObjectId >> 8); + span[7] = (byte)playerObjectId; + span[8] = state; + return span.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + public async ValueTask SetCrownSwitchInfoAsync(ushort switchObjectId, byte switchState, byte joinSide, string guildName, string playerName) + { + var connection = this._player.Connection; + if (connection is null) + { + return; + } + + int WritePacket() + { + // C1 1B B2 20 + var span = connection.Output.GetSpan(27)[..27]; + span.Clear(); + span[0] = 0xC1; + span[1] = 0x1B; + span[2] = 0xB2; + span[3] = 0x20; + span[4] = (byte)(switchObjectId >> 8); + span[5] = (byte)switchObjectId; + span[6] = switchState; + span[7] = joinSide; + WriteName(guildName, span.Slice(8, 8)); + WriteName(playerName, span.Slice(16, 10)); + return span.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + /// public async ValueTask AnnounceSealCapturedAsync(string guildName) { diff --git a/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs index 00e024a..ddee705 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/Events/CastleSiegeInitializer.cs @@ -46,7 +46,7 @@ internal sealed class CastleSiegeInitializer : InitializerBase var configuration = this.Context.CreateNew(); configuration.Enabled = true; - configuration.CrownHoldTimeSeconds = 30; + configuration.CrownHoldTimeSeconds = 60; // The client's crown registration panel counts down from 60s. configuration.RegisterMinLevel = 200; configuration.RegisterMinMembers = 20; configuration.ParticipantRewardMinSeconds = 60; diff --git a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/ApplyPendingUpdatesTool.cs b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/ApplyPendingUpdatesTool.cs new file mode 100644 index 0000000..4ff0e5e --- /dev/null +++ b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/ApplyPendingUpdatesTool.cs @@ -0,0 +1,58 @@ +// +// 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.Interfaces; +using MUnique.OpenMU.Persistence.EntityFramework; +using MUnique.OpenMU.Persistence.EntityFramework.Json; +using MUnique.OpenMU.Persistence.Initialization.Updates; +using MUnique.OpenMU.PlugIns; + +/// +/// A manual tool, not a test: applies the pending configuration updates to the database configured in +/// ConnectionSettings.xml - the same thing the admin panel's "Updates" page does, for when there is +/// no browser at hand. It is so it never runs in a normal test pass. +/// +[TestFixture] +[Explicit("Writes to the configured database. Run it deliberately, not as part of the test suite.")] +internal class ApplyPendingUpdatesTool +{ + /// + /// Applies every configuration update which is not installed yet. + /// + [Test] + public async Task ApplyPendingUpdatesAsync() + { + // The server registers these at startup; without them the configuration JSON cannot be read back. + JsonConverterRegistry.RegisterConverter(new LocalizedStringJsonConverter()); + JsonConverterRegistry.RegisterConverter(new BinaryAsHexJsonConverter()); + + var loggerFactory = new NullLoggerFactory(); + var contextProvider = new PersistenceContextProvider(loggerFactory, null); + var plugInManager = new PlugInManager(null, loggerFactory, null, null); + plugInManager.DiscoverAndRegisterPlugIns(); + + var service = new DataUpdateService(contextProvider, plugInManager); + var pending = (await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false)).ToList(); + TestContext.Out.WriteLine($"Pending updates: {pending.Count}"); + foreach (var update in pending) + { + TestContext.Out.WriteLine($" {(int)update.Version} - {update.Name}"); + } + + if (pending.Count == 0) + { + return; + } + + var progress = new Progress<(UpdateVersion CurrentUpdatingVersion, bool IsCompleted)>( + p => TestContext.Out.WriteLine($" applying {(int)p.CurrentUpdatingVersion} completed={p.IsCompleted}")); + await service.ApplyUpdatesAsync(pending, progress).ConfigureAwait(false); + + var left = await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false); + Assert.That(left, Is.Empty, "all updates should be installed now"); + } +} diff --git a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs index b7f40be..d0407c1 100644 --- a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs +++ b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs @@ -7,6 +7,7 @@ namespace MUnique.OpenMU.Persistence.Initialization.Tests; using Microsoft.Extensions.Logging.Abstractions; using MUnique.OpenMU.DataModel; using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.DataModel.Entities; using MUnique.OpenMU.GameLogic; using MUnique.OpenMU.Persistence.EntityFramework; using MUnique.OpenMU.Persistence.Initialization.Updates; @@ -91,6 +92,48 @@ internal class TestInitializationWithEfCore Assert.That(groups[0].PossibleItems.Single().Number, Is.EqualTo((short)14)); } + /// + /// Tests that the Castle Siege update writes its configuration into an existing Season 6 database, and + /// that applying it twice does not duplicate anything. This is the update which existing servers run to + /// get the castle: without it there is no Castle Siege configuration for the event to read. + /// + [Test] + public async Task TestSeason6CastleSiegeUpdatePlugInAsync() + { + var contextProvider = new InMemoryPersistenceContextProvider(); + var dataInitialization = new VersionSeasonSix.DataInitialization(contextProvider, new NullLoggerFactory()); + await dataInitialization.CreateInitialDataAsync(1, true).ConfigureAwait(false); + + using var context = contextProvider.CreateNewContext(); + var gameConfiguration = (await context.GetAsync().ConfigureAwait(false)).First(); + gameConfiguration.CastleSiegeConfiguration = null; + + var update = new AddCastleSiegeDataUpdatePlugIn(); + await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false); + await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false); + + var castleSiege = gameConfiguration.CastleSiegeConfiguration; + Assert.That(castleSiege, Is.Not.Null); + Assert.That(castleSiege!.Enabled, Is.True); + + // The client's crown registration panel counts down from 60 seconds, so the server has to match it. + Assert.That(castleSiege.CrownHoldTimeSeconds, Is.EqualTo(60)); + + // Both Crown Switches, the crown and the throne have to be there, or the siege cannot be finished. + var npcNumbers = castleSiege.NpcDefinitions.Select(n => n.MonsterDefinition?.Number).ToList(); + Assert.That(npcNumbers, Does.Contain((short)217), "Crown Switch 1"); + Assert.That(npcNumbers, Does.Contain((short)218), "Crown Switch 2"); + Assert.That(npcNumbers, Does.Contain((short)216), "Crown"); + + // Applying it twice must not double the NPC definitions. + Assert.That(npcNumbers.Count(n => n == 217), Is.EqualTo(1)); + Assert.That(npcNumbers.Count(n => n == 218), Is.EqualTo(1)); + + var data = (await context.GetAsync().ConfigureAwait(false)).ToList(); + Assert.That(data, Has.Count.EqualTo(1), "exactly one castle state row"); + Assert.That(data[0].IsOccupied, Is.False, "a fresh castle has no owner"); + } + /// /// Tests the data initialization using the in-memory persistence. /// diff --git a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs index 66ef0ec..6ca1cf7 100644 --- a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs +++ b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs @@ -4,143 +4,208 @@ namespace MUnique.OpenMU.Tests.CastleSiege; +using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic.CastleSiege; /// -/// Tests for the Castle Siege phase state machine (time-driven, injected clock). +/// Tests for the Castle Siege state machine (time-driven, injected clock). The cycle runs through the +/// original Season 6 state numbers the client knows: Idle1 -> RegisterGuild -> Ready -> Start -> End +/// -> EndCycle -> Idle1. Guilds are identified by their persistent id, not by their (renameable) name. /// [TestFixture] public class CastleSiegeContextTest { private static readonly DateTime T0 = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc); + private static readonly Guid GuildA = new("11111111-1111-1111-1111-111111111111"); + private static readonly Guid GuildB = new("22222222-2222-2222-2222-222222222222"); - /// Tests that a fresh context starts in the ownership (resting) phase. + /// Tests that a fresh context rests in the idle state. [Test] - public void StartsInOwnership() + public void StartsInIdle() { var ctx = new CastleSiegeContext(Config()); - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1)); } - /// Tests that force-starting moves the state machine into registration. + /// Tests that force-starting moves the state machine into guild registration. [Test] public async Task ForceStartMovesToRegistrationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration)); + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.RegisterGuild)); } - /// Tests that registration advances to preparation once its duration elapses. + /// Tests that registration advances to the preparation state once its duration elapses. [Test] - public async Task RegistrationAdvancesToPreparationAfterDurationAsync() + public async Task RegistrationAdvancesToReadyAfterDurationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); await ctx.TickAsync(T0.AddMinutes(4)); // still within registration - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration)); + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.RegisterGuild)); await ctx.TickAsync(T0.AddMinutes(5)); // registration duration elapsed - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Preparation)); + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Ready)); } - /// Tests a full cycle: registration -> preparation -> siege -> settlement -> ownership. + /// Tests a full cycle: register -> ready -> start -> end -> end cycle -> idle. [Test] - public async Task FullCycleReturnsToOwnershipAsync() + public async Task FullCycleReturnsToIdleAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); - await ctx.TickAsync(T0.AddMinutes(5)); // -> Preparation - await ctx.TickAsync(T0.AddMinutes(7)); // +2 prep -> Siege - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Siege)); - await ctx.TickAsync(T0.AddMinutes(17)); // +10 siege -> Settlement - await ctx.TickAsync(T0.AddMinutes(17)); // Settlement -> Ownership - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); + await ctx.TickAsync(T0.AddMinutes(5)); // -> Ready + await ctx.TickAsync(T0.AddMinutes(7)); // +2 preparation -> Start + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Start)); + await ctx.TickAsync(T0.AddMinutes(17)); // +10 siege -> End + await ctx.TickAsync(T0.AddMinutes(17)); // End -> EndCycle + await ctx.TickAsync(T0.AddMinutes(17)); // EndCycle -> Idle1 + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1)); } - /// Tests that guilds can be registered (by name) during the registration phase. + /// Tests that guilds are collected by id during the registration state. [Test] - public async Task RegisterGuildCollectsNamesDuringRegistrationAsync() + public async Task RegisterGuildCollectsGuildsDuringRegistrationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); - ctx.RegisterGuild("Attackers"); - Assert.That(ctx.RegisteredGuilds, Does.Contain("Attackers")); + ctx.RegisterGuild(GuildA, "Attackers"); + + Assert.That(ctx.IsRegistered(GuildA), Is.True); + Assert.That(ctx.RegisteredGuildNames, Does.Contain("Attackers")); } - /// Tests the full siege objective chain: destroy defenses, then hold both switches to capture the throne. + /// + /// Tests the full siege objective chain: operate both Crown Switches, which drops the crown's shield, + /// then hold the crown to take the throne, which becomes the castle ownership when the siege ends. + /// [Test] public async Task FullSiegeObjectiveChainToOwnershipAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); - ctx.SetDefenseCount(2); + await ctx.ForceStateAsync(CastleSiegeState.Start, T0); + var hold = TimeSpan.FromSeconds(60); - // Both switches held but defenses still up -> no capture. - ctx.SetSwitchHolder(217, "Attackers"); - ctx.SetSwitchHolder(218, "Attackers"); - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); + // A switch which is still being operated does not count yet. + StartSwitch(ctx, 217, GuildA, 1); + StartSwitch(ctx, 218, GuildA, 2); + Assert.That(ctx.GetShieldEligibleGuild(), Is.Null); - ctx.NotifyDefenseDestroyed(); - ctx.NotifyDefenseDestroyed(); + CompleteSwitch(ctx, 217); + Assert.That(ctx.GetShieldEligibleGuild(), Is.Null, "one completed switch is not enough"); - // Only one switch held -> still no capture. - ctx.SetSwitchHolder(218, null); - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); + CompleteSwitch(ctx, 218); + Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA)); - // Both switches held by the same guild + defenses down -> capture at the Sinior/Crown. - ctx.SetSwitchHolder(218, "Attackers"); - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.True); - Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers")); + // The crown only starts counting after the guild master clicked it. + Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.None)); + Assert.That(ctx.RequestCrownHold(GuildA), Is.True); + ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold); + Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured)); + Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA)); - await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement - await ctx.TickAsync(T0.AddMinutes(20)); // Settlement -> Ownership + await ctx.TickAsync(T0.AddMinutes(20)); // siege time is up: Start -> End + await ctx.TickAsync(T0.AddMinutes(20)); // End hands the castle to the guild on the throne + Assert.That(ctx.OwnerGuildId, Is.EqualTo(GuildA)); Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers")); } - /// Tests that two different guilds each holding one switch cannot capture the throne. + /// Tests that two different guilds each holding one switch keep the crown's shield up. [Test] - public async Task ThroneRequiresBothSwitchesBySameGuildAsync() + public async Task ShieldRequiresBothSwitchesBySameGuildAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); - ctx.SetDefenseCount(0); - ctx.SetSwitchHolder(217, "A"); - ctx.SetSwitchHolder(218, "B"); - Assert.That(ctx.TryCaptureThrone("A").Success, Is.False); - Assert.That(ctx.TryCaptureThrone("B").Success, Is.False); + await ctx.ForceStateAsync(CastleSiegeState.Start, T0); + StartSwitch(ctx, 217, GuildA, 1); + StartSwitch(ctx, 218, GuildB, 2); + CompleteSwitch(ctx, 217); + CompleteSwitch(ctx, 218); + + Assert.That(ctx.GetShieldEligibleGuild(), Is.Null); } - /// Tests that capturing the throne outside the siege phase is a no-op. + /// Tests that the switches don't work at all outside the running siege. [Test] - public void ThroneCaptureOutsideSiegeIsNoOp() + public void SwitchesDoNothingOutsideSiege() { var ctx = new CastleSiegeContext(Config()); - ctx.SetSwitchHolder(217, "A"); - ctx.SetSwitchHolder(218, "A"); - Assert.That(ctx.TryCaptureThrone("A").Success, Is.False); - Assert.That(ctx.OccupierGuildName, Is.Null); + var (result, _) = ctx.TryStartSwitchOperation(217, GuildA, "A", 1, "player", 100, T0); + + Assert.That(result, Is.EqualTo(CastleSiegeSwitchPush.SiegeNotRunning)); + Assert.That(ctx.GetSwitchOperation(217), Is.Null); + Assert.That(ctx.GetShieldEligibleGuild(), Is.Null); } - /// Tests that restoring persisted state sets phase/owner/registrations without raising PhaseChanged. + /// Tests that a switch belongs to the first player who clicked it, until they leave its area. [Test] - public void RestoreStateSetsStateWithoutFiringPhaseChanged() + public async Task SwitchIsTakenByOnePlayerUntilTheyLeaveAsync() { var ctx = new CastleSiegeContext(Config()); - var phaseChangedFired = false; - ctx.PhaseChanged += _ => phaseChangedFired = true; + await ctx.ForceStartRegistrationAsync(T0); + await ctx.ForceStateAsync(CastleSiegeState.Start, T0); + var push = TimeSpan.FromSeconds(15); - ctx.RestoreState("Winners", CastleSiegePhase.Ownership, T0, new[] { "Winners", "Losers" }); + var (first, _) = ctx.TryStartSwitchOperation(217, GuildA, "A", 1, "first", 100, T0); + Assert.That(first, Is.EqualTo(CastleSiegeSwitchPush.Started)); - Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); + // Somebody else clicking it is refused and learns who is on it. + var (second, blocking) = ctx.TryStartSwitchOperation(217, GuildB, "B", 2, "second", 100, T0.AddSeconds(1)); + Assert.That(second, Is.EqualTo(CastleSiegeSwitchPush.TakenByOther)); + Assert.That(blocking?.PlayerId, Is.EqualTo(1)); + + // It only counts once the push ran its time. + Assert.That(ctx.TickSwitch(217, true, T0.AddSeconds(14), push).Event, Is.EqualTo(CastleSiegeSwitchEvent.None)); + Assert.That(ctx.GetSwitchOperation(217)!.IsHeld, Is.False); + Assert.That(ctx.TickSwitch(217, true, T0.AddSeconds(15), push).Event, Is.EqualTo(CastleSiegeSwitchEvent.Held)); + Assert.That(ctx.GetSwitchOperation(217)!.IsHeld, Is.True); + + // Leaving the area frees it for everybody. + var (released, freed) = ctx.TickSwitch(217, false, T0.AddSeconds(20), push); + Assert.That(released, Is.EqualTo(CastleSiegeSwitchEvent.Released)); + Assert.That(freed?.PlayerId, Is.EqualTo(1)); + Assert.That(ctx.GetSwitchOperation(217), Is.Null); + + var (afterRelease, _) = ctx.TryStartSwitchOperation(217, GuildB, "B", 2, "second", 100, T0.AddSeconds(21)); + Assert.That(afterRelease, Is.EqualTo(CastleSiegeSwitchPush.Started)); + } + + /// Tests that losing a switch while the crown is being held drops the guild's eligibility. + [Test] + public async Task LosingASwitchRaisesTheShieldAgainAsync() + { + var ctx = await SiegeWithSwitchesHeldAsync(GuildA); + Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA)); + + ctx.TickSwitch(218, false, T0.AddSeconds(20), TimeSpan.FromSeconds(15)); + Assert.That(ctx.GetShieldEligibleGuild(), Is.Null); + } + + /// Tests that restoring persisted state sets state/owner/registrations without raising StateChanged. + [Test] + public void RestoreStateSetsStateWithoutFiringStateChanged() + { + var ctx = new CastleSiegeContext(Config()); + var stateChangedFired = false; + ctx.StateChanged += _ => stateChangedFired = true; + + ctx.RestoreState( + GuildA, + "Winners", + CastleSiegeState.Idle1, + T0, + new[] { new KeyValuePair(GuildA, "Winners"), new KeyValuePair(GuildB, "Losers") }); + + Assert.That(ctx.State, Is.EqualTo(CastleSiegeState.Idle1)); + Assert.That(ctx.OwnerGuildId, Is.EqualTo(GuildA)); Assert.That(ctx.OwnerGuildName, Is.EqualTo("Winners")); - Assert.That(ctx.RegisteredGuilds, Is.EquivalentTo(new[] { "Winners", "Losers" })); - Assert.That(phaseChangedFired, Is.False); + Assert.That(ctx.RegisteredGuildNames, Is.EquivalentTo(new[] { "Winners", "Losers" })); + Assert.That(stateChangedFired, Is.False); Assert.That(ctx.ConsumeDirty(), Is.False, "restore must not mark the state dirty"); } - /// Tests that the weekly auto-schedule (stored in config) only fires on a matching day/time window. + /// Tests that the weekly auto-schedule (stored in the settings) only fires on a matching day/time window. [Test] public void ScheduleFiresOnMatchingDayAndTimeWindow() { @@ -149,7 +214,7 @@ public class CastleSiegeContextTest var config = ctx.Configuration; var sunday = new DateTime(2026, 1, 4, 20, 0, 2, DateTimeKind.Utc); // a Sunday, +2s into the window - var sundayLate = new DateTime(2026, 1, 4, 20, 0, 30, DateTimeKind.Utc); // past the 5s window + var sundayLate = new DateTime(2026, 1, 4, 20, 0, 30, DateTimeKind.Utc); // past the window var monday = new DateTime(2026, 1, 5, 20, 0, 2, DateTimeKind.Utc); // wrong day Assert.That(config.IsRegistrationOpenTime(sunday), Is.True); @@ -168,26 +233,26 @@ public class CastleSiegeContextTest var ctx = new CastleSiegeContext(Config()); Assert.That(ctx.ConsumeDirty(), Is.False, "a fresh context has nothing to persist"); - await ctx.ForceStartRegistrationAsync(T0); // phase transition -> dirty + await ctx.ForceStartRegistrationAsync(T0); // state transition -> dirty Assert.That(ctx.ConsumeDirty(), Is.True); Assert.That(ctx.ConsumeDirty(), Is.False, "ConsumeDirty resets the flag"); - ctx.RegisterGuild("Attackers"); // registration -> dirty + ctx.RegisterGuild(GuildA, "Attackers"); // registration -> dirty Assert.That(ctx.ConsumeDirty(), Is.True); - ctx.SetOwner("Attackers"); // owner change -> dirty + ctx.SetOwner(GuildA, "Attackers"); // owner change -> dirty Assert.That(ctx.ConsumeDirty(), Is.True); } /// Tests that the remaining siege time counts down during the siege and is zero otherwise. [Test] - public async Task RemainingSiegeTimeReflectsSiegePhaseAsync() + public async Task RemainingSiegeTimeReflectsSiegeStateAsync() { var ctx = new CastleSiegeContext(Config()); // 10 minute siege duration Assert.That(ctx.GetRemainingSiegeTime(T0), Is.EqualTo(TimeSpan.Zero), "no siege running -> zero"); await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); + await ctx.ForceStateAsync(CastleSiegeState.Start, T0); Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(3)), Is.EqualTo(TimeSpan.FromMinutes(7))); Assert.That(ctx.GetRemainingSiegeTime(T0.AddMinutes(15)), Is.EqualTo(TimeSpan.Zero), "past the end -> clamped to zero"); @@ -197,75 +262,90 @@ public class CastleSiegeContextTest [Test] public async Task CrownHoldCapturesAfterHoldDurationAsync() { - var ctx = new CastleSiegeContext(Config()); - await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); - ctx.SetDefenseCount(0); - ctx.SetSwitchHolder(217, "Attackers"); - ctx.SetSwitchHolder(218, "Attackers"); - Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo("Attackers")); - + var ctx = await SiegeWithSwitchesHeldAsync(GuildA); var hold = TimeSpan.FromSeconds(60); - Assert.That(ctx.TickCrownHold("Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted)); - Assert.That(ctx.TickCrownHold("Attackers", true, T0.AddSeconds(30), hold).Event, Is.EqualTo(CrownEvent.None)); - Assert.That(ctx.OccupierGuildName, Is.Null); - var captured = ctx.TickCrownHold("Attackers", true, T0.AddSeconds(60), hold); + Assert.That(ctx.GetShieldEligibleGuild(), Is.EqualTo(GuildA)); + Assert.That(ctx.RequestCrownHold(GuildA), Is.True); + Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted)); + Assert.That(ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(30), hold).Event, Is.EqualTo(CrownEvent.None)); + Assert.That(ctx.OccupierGuildId, Is.Null); + + var captured = ctx.TickCrownHold(GuildA, "Attackers", true, T0.AddSeconds(60), hold); Assert.That(captured.Event, Is.EqualTo(CrownEvent.Captured)); Assert.That(captured.ShieldDown, Is.True); - Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers")); + Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA)); } /// Tests that losing a switch mid-hold resets the crown-hold progress (contestable). [Test] public async Task CrownHoldResetsWhenSwitchLostAsync() { - var ctx = new CastleSiegeContext(Config()); - await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); - ctx.SetDefenseCount(0); - ctx.SetSwitchHolder(217, "A"); - ctx.SetSwitchHolder(218, "A"); + var ctx = await SiegeWithSwitchesHeldAsync(GuildA); + var hold = TimeSpan.FromSeconds(60); - Assert.That(ctx.TickCrownHold("A", true, T0, TimeSpan.FromSeconds(60)).Event, Is.EqualTo(CrownEvent.HoldStarted)); + Assert.That(ctx.RequestCrownHold(GuildA), Is.True); + Assert.That(ctx.TickCrownHold(GuildA, "A", true, T0, hold).Event, Is.EqualTo(CrownEvent.HoldStarted)); - ctx.SetSwitchHolder(218, null); // lost a switch -> no longer eligible - var reset = ctx.TickCrownHold(ctx.GetShieldEligibleGuild(), false, T0.AddSeconds(10), TimeSpan.FromSeconds(60)); + ctx.TickSwitch(218, false, T0.AddSeconds(5), TimeSpan.FromSeconds(15)); // lost a switch -> no longer eligible + var reset = ctx.TickCrownHold(ctx.GetShieldEligibleGuild(), null, false, T0.AddSeconds(10), hold); Assert.That(reset.ShieldDown, Is.False); Assert.That(reset.Event, Is.EqualTo(CrownEvent.HoldReset)); - Assert.That(ctx.OccupierGuildName, Is.Null); + Assert.That(ctx.OccupierGuildId, Is.Null); } /// Tests that the occupier can't re-capture its own throne, but a different guild can contest it. [Test] public async Task OccupierDoesNotRecaptureButAnotherGuildCanContestAsync() { - var ctx = new CastleSiegeContext(Config()); - await ctx.ForceStartRegistrationAsync(T0); - await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); - ctx.SetDefenseCount(0); + var ctx = await SiegeWithSwitchesHeldAsync(GuildA); var hold = TimeSpan.FromSeconds(60); + var push = TimeSpan.FromSeconds(15); - ctx.SetSwitchHolder(217, "A"); - ctx.SetSwitchHolder(218, "A"); - ctx.TickCrownHold("A", true, T0, hold); - Assert.That(ctx.TickCrownHold("A", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured)); - Assert.That(ctx.OccupierGuildName, Is.EqualTo("A")); + ctx.RequestCrownHold(GuildA); + ctx.TickCrownHold(GuildA, "A", true, T0, hold); + Assert.That(ctx.TickCrownHold(GuildA, "A", true, T0.AddSeconds(60), hold).Event, Is.EqualTo(CrownEvent.Captured)); + Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildA)); - // A keeps holding — no re-registration loop, but the shield stays down (they hold it). - var after = ctx.TickCrownHold("A", true, T0.AddSeconds(61), hold); + // A keeps holding - no re-registration loop, but the shield stays down (they hold it). + Assert.That(ctx.RequestCrownHold(GuildA), Is.False, "the occupier cannot re-register its own throne"); + var after = ctx.TickCrownHold(GuildA, "A", true, T0.AddSeconds(61), hold); Assert.That(after.Event, Is.EqualTo(CrownEvent.None)); Assert.That(after.ShieldDown, Is.True); // B takes both switches and can contest/capture. - ctx.SetSwitchHolder(217, "B"); - ctx.SetSwitchHolder(218, "B"); - Assert.That(ctx.TickCrownHold("B", true, T0.AddSeconds(62), hold).Event, Is.EqualTo(CrownEvent.HoldStarted)); - Assert.That(ctx.TickCrownHold("B", true, T0.AddSeconds(122), hold).Event, Is.EqualTo(CrownEvent.Captured)); - Assert.That(ctx.OccupierGuildName, Is.EqualTo("B")); + ctx.TickSwitch(217, false, T0.AddSeconds(61), push); + ctx.TickSwitch(218, false, T0.AddSeconds(61), push); + StartSwitch(ctx, 217, GuildB, 3); + StartSwitch(ctx, 218, GuildB, 4); + CompleteSwitch(ctx, 217); + CompleteSwitch(ctx, 218); + + Assert.That(ctx.RequestCrownHold(GuildB), Is.True); + Assert.That(ctx.TickCrownHold(GuildB, "B", true, T0.AddSeconds(62), hold).Event, Is.EqualTo(CrownEvent.HoldStarted)); + Assert.That(ctx.TickCrownHold(GuildB, "B", true, T0.AddSeconds(122), hold).Event, Is.EqualTo(CrownEvent.Captured)); + Assert.That(ctx.OccupierGuildId, Is.EqualTo(GuildB)); } - private static CastleSiegeConfiguration Config() => new() + private static async Task SiegeWithSwitchesHeldAsync(Guid guildId) + { + var ctx = new CastleSiegeContext(Config()); + await ctx.ForceStartRegistrationAsync(T0); + await ctx.ForceStateAsync(CastleSiegeState.Start, T0); + StartSwitch(ctx, 217, guildId, 1); + StartSwitch(ctx, 218, guildId, 2); + CompleteSwitch(ctx, 217); + CompleteSwitch(ctx, 218); + return ctx; + } + + private static void StartSwitch(CastleSiegeContext ctx, short switchNumber, Guid guildId, ushort playerId) + => ctx.TryStartSwitchOperation(switchNumber, guildId, guildId.ToString()[..4], playerId, $"p{playerId}", (ushort)switchNumber, T0); + + private static void CompleteSwitch(CastleSiegeContext ctx, short switchNumber) + => ctx.TickSwitch(switchNumber, true, T0.AddSeconds(30), TimeSpan.FromSeconds(15)); + + private static CastleSiegeSettings Config() => new() { RegistrationDuration = TimeSpan.FromMinutes(5), PreparationDuration = TimeSpan.FromMinutes(2),