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/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/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/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs index 4a5ef2e..67eec74 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs @@ -4,109 +4,151 @@ 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. -/// 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. +/// +/// 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: 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 { /// 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 _switches = 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 Guid? _crownHoldRequestedBy; 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 +156,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,83 +239,127 @@ 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 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 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). /// - public string? GetShieldEligibleGuild() + /// The current UTC time. + public TimeSpan GetRemainingStateTime(DateTime nowUtc) { - if (this.Phase != CastleSiegePhase.Siege || this._defensesRemaining > 0) + 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; + } + + /// + /// 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) { 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. /// 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); + 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; - return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null); + this.ResetCrownHold(); + 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._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this._occupierName = eligibleGuildName; + this.ResetCrownHold(); 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 +374,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; @@ -317,79 +413,124 @@ 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 name, or null. - public void SetSwitchHolder(short switchNumber, string? guildName) + /// 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.Phase == CastleSiegePhase.Siege && this._switchHolders.ContainsKey(switchNumber)) + if (!this.IsSiegeRunning || !this._switches.ContainsKey(switchNumber)) { - this._switchHolders[switchNumber] = guildName; + 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 name. - /// Whether it succeeded and a human-readable reason/result message. - public (bool Success, string Reason) TryCaptureThrone(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.Phase != CastleSiegePhase.Siege) + 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 not null) + if (!this.IsSiegeRunning || !operatorPresent) { - return (false, this._occupier == guildName - ? "Your guild already holds the throne." - : $"The throne is already held by '{this._occupier}'."); + 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] != guildName || this._switchHolders[218] != guildName) - { - return (false, "Your guild must be holding BOTH Crown Switches at once (stand a member on each)."); - } - - this._occupier = guildName; - return (true, "throne captured"); + return (CastleSiegeSwitchEvent.None, operation); } /// 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 Guid? GetHeldSwitchGuild(short switchNumber) + => this._switches[switchNumber] is { IsHeld: true } operation ? operation.GuildId : null; + + private string DescribeSwitch(short switchNumber) + => 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._crownHoldGuild = null; - this._crownHoldStartUtc = null; + this._occupierName = null; + this.ResetCrownHold(); 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 +555,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 70% rename from src/GameLogic/CastleSiege/CastleSiegeConfiguration.cs rename to src/GameLogic/CastleSiege/CastleSiegeSettings.cs index dfe54a0..5e98ca9 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,23 @@ 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); + /// + /// 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 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. + // --- 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. - /// 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/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 891af70..5ea1a42 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeThroneCaptureTalkPlugIn.cs @@ -43,56 +43,58 @@ 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); - } - - private static string DescribeThroneStep(CastleSiegeContext context, string guildName) - { - if (context.Phase != CastleSiegePhase.Siege) + // 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)) { - return "The siege is not running yet."; + await ShowAsync(player, "Hold the Crown - do not step away until the seal is registered!").ConfigureAwait(false); + return; } - if (context.DefensesRemaining > 0) + await ShowAsync(player, DescribeThroneStep(context, guild.Id, isGuildMaster)).ConfigureAwait(false); + } + + private static string DescribeThroneStep(CastleSiegeContext context, Guid guildId, bool isGuildMaster) + { + if (!context.IsSiegeRunning) { - return $"Destroy all castle gates first ({context.DefensesRemaining} remaining), then hold both Crown Switches."; + return "The siege is not running yet."; } 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 == 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 "Another guild is holding both switches. Take a switch back to raise their shield."; } - return $"Guild '{eligible}' 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/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..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 { @@ -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..731a37c 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,69 @@ 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; + /// 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(); - private string? _cachedFlagOwner; + /// + /// 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. + /// + 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 +104,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) + if (GetCounters(gameContext).NextCastleFlag()) { + await LoadPersistedStateAsync(gameContext, context).ConfigureAwait(false); await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); } @@ -143,19 +187,19 @@ 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). - if (DateTime.UtcNow.Second % 15 == 0) + // Keep the owner guild's logo painted on the castle flags for anyone on the castle map (any state). + if (GetCounters(gameContext).NextCastleFlag()) { await this.BroadcastCastleFlagAsync(gameContext, context).ConfigureAwait(false); } @@ -171,25 +215,98 @@ 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: - // Stop the on-map countdown for everyone still on the battle map. + case CastleSiegeState.End: + // 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 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 +316,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 +341,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 +379,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,54 +395,144 @@ 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 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 + .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) + 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) { - string? holder = null; - var nearby = map.GetAttackablesInRange(new Point(x, y), SwitchHoldRange).OfType(); - foreach (var player in nearby) + if (context.GetSwitchOperation(switchNumber) is not { } operation) { - var guildName = await GetGuildNameAsync(player).ConfigureAwait(false); - if (guildName is not null && context.RegisteredGuilds.Contains(guildName)) - { - holder = guildName; - break; - } + continue; } - context.SetSwitchHolder(switchNumber, holder); + 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) + { + continue; + } + + 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 gate down, the crown shield drops; - // that guild's master then holds the crown for CrownHoldDuration to take the throne (contestable). + // 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; - 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 +543,25 @@ 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. 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.Guild is { } captured: + 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; @@ -428,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.PhaseStartedUtc).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); @@ -464,22 +606,155 @@ 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 ? 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 +775,58 @@ 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) + /// + /// Counts the ticks between the periodic broadcasts of one game context. + /// + private sealed class BroadcastCounters { - try + 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) { - var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); - if (map?.SafeZoneSpawnGate is not { } gate) + if (++counter < period) { - return; + return false; } - 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); + counter = 0; + return true; } - 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(); } } 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/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/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/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/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/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/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/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..ddee705 --- /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 = 60; // The client's crown registration panel counts down from 60s. + 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() 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/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.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; + } + } + } +} 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),