diff --git a/src/DataModel/Configuration/AreaSkillSettings.cs b/src/DataModel/Configuration/AreaSkillSettings.cs
index 987e5a942..726019f17 100644
--- a/src/DataModel/Configuration/AreaSkillSettings.cs
+++ b/src/DataModel/Configuration/AreaSkillSettings.cs
@@ -80,6 +80,14 @@ public partial class AreaSkillSettings
///
public float HitChancePerDistanceMultiplier { get; set; }
+ ///
+ /// Gets or sets the number of projectiles/arrows that are fired.
+ /// When greater than 1, the projectiles are evenly distributed within the frustum.
+ /// Each target can only be hit by projectiles whose paths cross the target's position.
+ /// Default is 1 (single projectile).
+ ///
+ public int ProjectileCount { get; set; }
+
///
public override string ToString()
{
diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
index caf326a3d..ba6e70122 100644
--- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
+++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
@@ -57,6 +57,73 @@ public async ValueTask AttackAsync(Player player, ushort extraTargetId, ushort s
await player.ForEachWorldObserverAsync(p => p.ShowAreaSkillAnimationAsync(player, skill, targetAreaCenter, rotation), true).ConfigureAwait(false);
}
+ private static bool AreaSkillSettingsAreDefault([NotNullWhen(true)] AreaSkillSettings? settings)
+ {
+ if (settings is null)
+ {
+ return true;
+ }
+
+ return !settings.UseDeferredHits
+ && settings.DelayPerOneDistance <= TimeSpan.Zero
+ && settings.MinimumNumberOfHitsPerTarget == 1
+ && settings.MaximumNumberOfHitsPerTarget == 1
+ && settings.MaximumNumberOfHitsPerAttack == 0
+ && Math.Abs(settings.HitChancePerDistanceMultiplier - 1.0) <= 0.00001f;
+ }
+
+ private static IEnumerable GetTargets(Player player, Point targetAreaCenter, Skill skill, byte rotation, ushort extraTargetId)
+ {
+ var isExtraTargetDefined = extraTargetId != UndefinedTarget;
+ var extraTarget = isExtraTargetDefined ? player.GetObject(extraTargetId) as IAttackable : null;
+
+ if (skill.SkillType == SkillType.AreaSkillExplicitTarget)
+ {
+ if (extraTarget?.CheckSkillTargetRestrictions(player, skill) is true
+ && player.IsInRange(extraTarget.Position, skill.Range + 2)
+ && !extraTarget.IsAtSafezone())
+ {
+ yield return extraTarget;
+ }
+
+ yield break;
+ }
+
+ foreach (var target in GetTargetsInRange(player, targetAreaCenter, skill, rotation))
+ {
+ yield return target;
+ }
+ }
+
+ private static IEnumerable GetTargetsInRange(Player player, Point targetAreaCenter, Skill skill, byte rotation)
+ {
+ var targetsInRange = player.CurrentMap?
+ .GetAttackablesInRange(targetAreaCenter, skill.Range)
+ .Where(a => a != player)
+ .Where(a => !a.IsAtSafezone())
+ ?? [];
+
+ if (skill.AreaSkillSettings is { UseFrustumFilter: true } areaSkillSettings)
+ {
+ var filter = FrustumFilters.GetOrAdd(areaSkillSettings, static s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance, s.ProjectileCount > 0 ? s.ProjectileCount : 1));
+ targetsInRange = targetsInRange.Where(a => filter.IsTargetWithinBounds(player, a, rotation));
+ }
+
+ if (skill.AreaSkillSettings is { UseTargetAreaFilter: true })
+ {
+ targetsInRange = targetsInRange.Where(a => a.GetDistanceTo(targetAreaCenter) < skill.AreaSkillSettings.TargetAreaDiameter * 0.5f);
+ }
+
+ if (!player.GameContext.Configuration.AreaSkillHitsPlayer)
+ {
+ targetsInRange = targetsInRange.Where(a => a is not Player);
+ }
+
+ targetsInRange = targetsInRange.Where(target => target.CheckSkillTargetRestrictions(player, skill));
+
+ return targetsInRange;
+ }
+
private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTargetId, Point targetAreaCenter, SkillEntry skillEntry, Skill skill, byte rotation)
{
if (player.Attributes is not { } attributes)
@@ -111,7 +178,7 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
}
else
{
- extraTarget = await this.AttackTargetsAsync(player, extraTargetId, targetAreaCenter, skillEntry, areaSkillSettings, targets, isCombo).ConfigureAwait(false);
+ extraTarget = await this.AttackTargetsAsync(player, extraTargetId, targetAreaCenter, skillEntry, areaSkillSettings, targets, rotation, isCombo).ConfigureAwait(false);
}
if (isCombo)
@@ -120,134 +187,105 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
}
}
- private async Task AttackTargetsAsync(Player player, ushort extraTargetId, Point targetAreaCenter, SkillEntry skillEntry, AreaSkillSettings areaSkillSettings, IEnumerable targets, bool isCombo)
+ private async Task AttackTargetsAsync(Player player, ushort extraTargetId, Point targetAreaCenter, SkillEntry skillEntry, AreaSkillSettings areaSkillSettings, IEnumerable targets, byte rotation, bool isCombo)
{
IAttackable? extraTarget = null;
var attackCount = 0;
var maxAttacks = areaSkillSettings.MaximumNumberOfHitsPerAttack == 0 ? int.MaxValue : areaSkillSettings.MaximumNumberOfHitsPerAttack;
var currentDelay = TimeSpan.Zero;
- for (int attackRound = 0; attackRound < areaSkillSettings.MaximumNumberOfHitsPerTarget; attackRound++)
+ // Order targets by distance to process nearest targets first
+ var orderedTargets = targets.ToList();
+ FrustumBasedTargetFilter? filter = null;
+ var projectileCount = 1;
+ var attackRounds = areaSkillSettings.MaximumNumberOfHitsPerTarget;
+
+ if (areaSkillSettings is { UseFrustumFilter: true, ProjectileCount: > 1 })
+ {
+ orderedTargets.Sort((a, b) => player.GetDistanceTo(a).CompareTo(player.GetDistanceTo(b)));
+ filter = FrustumFilters.GetOrAdd(areaSkillSettings, static s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance, s.ProjectileCount));
+ projectileCount = areaSkillSettings.ProjectileCount;
+ attackRounds = 1; // One attack round per projectile
+
+ extraTarget = orderedTargets.FirstOrDefault(t => t.Id == extraTargetId);
+ if (extraTarget is not null)
+ {
+ // In this case we just calculate the angle on server side, so that lags
+ // or desynced positions may not have such a big impact
+ var angle = (float)player.Position.GetAngleDegreeTo(extraTarget.Position);
+ rotation = (byte)((angle / 360.0f) * 256.0f);
+ }
+ }
+
+ // Process each projectile separately
+ for (int projectileIndex = 0; projectileIndex < projectileCount; projectileIndex++)
{
if (attackCount >= maxAttacks)
{
break;
}
- foreach (var target in targets)
+ for (int attackRound = 0; attackRound < attackRounds; attackRound++)
{
if (attackCount >= maxAttacks)
{
break;
}
- if (target.Id == extraTargetId)
- {
- extraTarget = target;
- }
-
- var hitChance = attackRound < areaSkillSettings.MinimumNumberOfHitsPerTarget
- ? 1.0
- : Math.Min(areaSkillSettings.HitChancePerDistanceMultiplier, Math.Pow(areaSkillSettings.HitChancePerDistanceMultiplier, player.GetDistanceTo(target)));
- if (hitChance < 1.0 && !Rand.NextRandomBool(hitChance))
+ foreach (var target in orderedTargets)
{
- continue;
- }
-
- var distanceDelay = areaSkillSettings.DelayPerOneDistance * player.GetDistanceTo(target);
- var attackDelay = currentDelay + distanceDelay;
- attackCount++;
-
- if (attackDelay == TimeSpan.Zero)
- {
- await this.ApplySkillAsync(player, skillEntry, target, targetAreaCenter, isCombo).ConfigureAwait(false);
- }
- else
- {
- // The most pragmatic approach is just spawning a Task for each hit.
- // We have to see, how this works out in terms of performance.
- _ = Task.Run(async () =>
+ if (attackCount >= maxAttacks)
{
- await Task.Delay(attackDelay).ConfigureAwait(false);
- if (!target.IsAtSafezone() && target.IsActive())
- {
- await this.ApplySkillAsync(player, skillEntry, target, targetAreaCenter, isCombo).ConfigureAwait(false);
- }
- });
- }
- }
+ break;
+ }
- currentDelay += areaSkillSettings.DelayBetweenHits;
- }
+ if (target.Id == extraTargetId)
+ {
+ extraTarget = target;
+ }
- return extraTarget;
- }
+ // For multiple projectiles, check if this specific projectile can hit the target
+ if (filter != null && !filter.IsTargetWithinBounds(player, target, rotation, projectileIndex))
+ {
+ continue; // This projectile cannot hit this target
+ }
- private static bool AreaSkillSettingsAreDefault([NotNullWhen(true)] AreaSkillSettings? settings)
- {
- if (settings is null)
- {
- return true;
- }
+ var hitChance = attackRound < areaSkillSettings.MinimumNumberOfHitsPerTarget
+ ? 1.0
+ : Math.Min(areaSkillSettings.HitChancePerDistanceMultiplier, Math.Pow(areaSkillSettings.HitChancePerDistanceMultiplier, player.GetDistanceTo(target)));
+ if (hitChance < 1.0 && !Rand.NextRandomBool(hitChance))
+ {
+ continue;
+ }
- return !settings.UseDeferredHits
- && settings.DelayPerOneDistance <= TimeSpan.Zero
- && settings.MinimumNumberOfHitsPerTarget == 1
- && settings.MaximumNumberOfHitsPerTarget == 1
- && settings.MaximumNumberOfHitsPerAttack == 0
- && Math.Abs(settings.HitChancePerDistanceMultiplier - 1.0) <= 0.00001f;
- }
+ var distanceDelay = areaSkillSettings.DelayPerOneDistance * player.GetDistanceTo(target);
+ var attackDelay = currentDelay + distanceDelay;
+ attackCount++;
- private static IEnumerable GetTargets(Player player, Point targetAreaCenter, Skill skill, byte rotation, ushort extraTargetId)
- {
- var isExtraTargetDefined = extraTargetId != UndefinedTarget;
- var extraTarget = isExtraTargetDefined ? player.GetObject(extraTargetId) as IAttackable : null;
+ if (attackDelay == TimeSpan.Zero)
+ {
+ await this.ApplySkillAsync(player, skillEntry, target, targetAreaCenter, isCombo).ConfigureAwait(false);
+ }
+ else
+ {
+ // The most pragmatic approach is just spawning a Task for each hit.
+ // We have to see, how this works out in terms of performance.
+ _ = Task.Run(async () =>
+ {
+ await Task.Delay(attackDelay).ConfigureAwait(false);
+ if (!target.IsAtSafezone() && target.IsActive())
+ {
+ await this.ApplySkillAsync(player, skillEntry, target, targetAreaCenter, isCombo).ConfigureAwait(false);
+ }
+ });
+ }
+ }
- if (skill.SkillType == SkillType.AreaSkillExplicitTarget)
- {
- if (extraTarget?.CheckSkillTargetRestrictions(player, skill) is true
- && player.IsInRange(extraTarget.Position, skill.Range + 2)
- && !extraTarget.IsAtSafezone())
- {
- yield return extraTarget;
+ currentDelay += areaSkillSettings.DelayBetweenHits;
}
-
- yield break;
- }
-
- foreach (var target in GetTargetsInRange(player, targetAreaCenter, skill, rotation))
- {
- yield return target;
}
- }
-
- private static IEnumerable GetTargetsInRange(Player player, Point targetAreaCenter, Skill skill, byte rotation)
- {
- var targetsInRange = player.CurrentMap?
- .GetAttackablesInRange(targetAreaCenter, skill.Range)
- .Where(a => a != player)
- .Where(a => !a.IsAtSafezone())
- ?? [];
- if (skill.AreaSkillSettings is { UseFrustumFilter: true } areaSkillSettings)
- {
- var filter = FrustumFilters.GetOrAdd(areaSkillSettings, static s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance));
- targetsInRange = targetsInRange.Where(a => filter.IsTargetWithinBounds(player, a, rotation));
- }
-
- if (skill.AreaSkillSettings is { UseTargetAreaFilter: true })
- {
- targetsInRange = targetsInRange.Where(a => a.GetDistanceTo(targetAreaCenter) < skill.AreaSkillSettings.TargetAreaDiameter * 0.5f);
- }
-
- if (!player.GameContext.Configuration.AreaSkillHitsPlayer)
- {
- targetsInRange = targetsInRange.Where(a => a is not Player);
- }
-
- targetsInRange = targetsInRange.Where(target => target.CheckSkillTargetRestrictions(player, skill));
-
- return targetsInRange;
+ return extraTarget;
}
private async ValueTask ApplySkillAsync(Player player, SkillEntry skillEntry, IAttackable target, Point targetAreaCenter, bool isCombo)
diff --git a/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs b/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs
index e68433b59..37c0006a6 100644
--- a/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs
+++ b/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs
@@ -13,6 +13,7 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills;
///
public record FrustumBasedTargetFilter
{
+ private const double DistanceEpsilon = 0.001;
private readonly Vector2[][] _rotationVectors;
///
@@ -21,11 +22,13 @@ public record FrustumBasedTargetFilter
/// The width of the frustum at the start.
/// The width of the frustum at the end.
/// The distance.
- public FrustumBasedTargetFilter(float startWidth, float endWidth, float distance)
+ /// The number of projectiles. Default is 1.
+ public FrustumBasedTargetFilter(float startWidth, float endWidth, float distance, int projectileCount = 1)
{
this.EndWidth = endWidth;
this.Distance = distance;
this.StartWidth = startWidth;
+ this.ProjectileCount = projectileCount;
this._rotationVectors = this.CalculateRotationVectors();
}
@@ -44,6 +47,11 @@ public FrustumBasedTargetFilter(float startWidth, float endWidth, float distance
///
public float StartWidth { get; }
+ ///
+ /// Gets the number of projectiles/arrows.
+ ///
+ public int ProjectileCount { get; }
+
///
/// Determines whether the target is within the hit bounds.
///
@@ -62,6 +70,104 @@ public bool IsTargetWithinBounds(ILocateable attacker, ILocateable target, byte
return IsWithinFrustum(frustum, target.Position);
}
+ ///
+ /// Determines whether the target is within the hit bounds for a specific projectile.
+ /// When multiple projectiles are used, they are evenly distributed within the frustum.
+ ///
+ /// The attacker.
+ /// The target.
+ /// The rotation.
+ /// The zero-based index of the projectile (0 to ProjectileCount-1).
+ /// true if the target is within hit bounds for the specified projectile; otherwise, false.
+ public bool IsTargetWithinBounds(ILocateable attacker, ILocateable target, byte rotation, int projectileIndex)
+ {
+ if (this.ProjectileCount <= 1)
+ {
+ // For single projectile, use the simple frustum check
+ return this.IsTargetWithinBounds(attacker, target, rotation);
+ }
+
+ if (projectileIndex < 0 || projectileIndex >= this.ProjectileCount)
+ {
+ return false;
+ }
+
+ // First check if target is within the overall frustum
+ if (!this.IsTargetWithinBounds(attacker, target, rotation))
+ {
+ return false;
+ }
+
+ // Calculate the relative position of the target within the frustum
+ var relativePosition = this.CalculateRelativePositionInFrustum(attacker.Position, target.Position, rotation);
+
+ // Divide the frustum into sections (-1 to 1 range)
+ // For 3 projectiles: left (-1 to -0.33), center (-0.33 to 0.33), right (0.33 to 1)
+ var sectionWidth = 2.0 / this.ProjectileCount;
+ var sectionStart = -1.0 + (projectileIndex * sectionWidth);
+ var sectionEnd = sectionStart + sectionWidth;
+
+ // Add overlap so targets near boundaries can be hit by adjacent projectiles
+ var overlap = this.GetOverlap(attacker.Position, target.Position);
+ sectionStart -= overlap;
+ sectionEnd += overlap;
+
+ return relativePosition >= sectionStart && relativePosition <= sectionEnd;
+ }
+
+ private double GetOverlap(Point attackerPos, Point targetPos)
+ {
+ var distance = attackerPos.EuclideanDistanceTo(targetPos);
+ if (distance == 0)
+ {
+ return 1;
+ }
+
+ // The overlap decreases over higher distance
+ var overlap = (1.0 / Math.Floor(distance)) / this.ProjectileCount;
+ overlap += 0.001; // Adding a small epsilon to make it slightly more tolerant in the comparisons
+ return overlap;
+ }
+
+ private double CalculateRelativePositionInFrustum(Point attackerPos, Point targetPos, byte rotation)
+ {
+ // Calculate the vector from attacker to target
+ var dx = targetPos.X - attackerPos.X;
+ var dy = targetPos.Y - attackerPos.Y;
+
+ // Calculate the rotation angle (same logic as in CalculateRotationVectors)
+ var rotationAngle = (rotation * 360.0 / 256.0) + 180; // Add 180 offset as in the frustum calculation
+ var rotationRad = rotationAngle * Math.PI / 180.0;
+
+ // Rotate the vector to align with the frustum's coordinate system
+ // The frustum has Y pointing forward and X pointing right
+ var cos = Math.Cos(-rotationRad);
+ var sin = Math.Sin(-rotationRad);
+ var rotatedX = dx * cos - dy * sin;
+ var rotatedY = dx * sin + dy * cos;
+
+ // If target is behind us or too close, return 0
+ if (rotatedY <= 0)
+ {
+ return 0;
+ }
+
+ // Calculate the frustum width at the target's distance
+ // Linear interpolation between start and end width
+ var distanceRatio = Math.Min(rotatedY / this.Distance, 1.0);
+ var frustumWidthAtDistance = this.StartWidth + (this.EndWidth - this.StartWidth) * distanceRatio;
+
+ // Normalize the X position by the frustum width at that distance
+ // Result will be in range [-1, 1] where -1 is left edge, 0 is center, 1 is right edge
+ if (Math.Abs(frustumWidthAtDistance) < DistanceEpsilon)
+ {
+ return 0;
+ }
+
+ var normalizedX = rotatedX / frustumWidthAtDistance;
+ return Math.Clamp(normalizedX, -1.0, 1.0);
+ }
+
private static bool IsWithinFrustum((Vector4 X, Vector4 Y) frustum, Point target)
{
var isOutOfRange = (((frustum.X.X - target.X) * (frustum.Y.W - target.Y)) - ((frustum.X.W - target.X) * (frustum.Y.X - target.Y))) < 0.0f
diff --git a/src/Persistence/EntityFramework/Migrations/20260103173108_AddProjectileCountToAreaSkillSettings.Designer.cs b/src/Persistence/EntityFramework/Migrations/20260103173108_AddProjectileCountToAreaSkillSettings.Designer.cs
new file mode 100644
index 000000000..988830b5a
--- /dev/null
+++ b/src/Persistence/EntityFramework/Migrations/20260103173108_AddProjectileCountToAreaSkillSettings.Designer.cs
@@ -0,0 +1,5179 @@
+//
+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("20260103173108_AddProjectileCountToAreaSkillSettings")]
+ partial class AddProjectileCountToAreaSkillSettings
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .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("IsTemplate")
+ .HasColumnType("boolean");
+
+ b.Property("IsVaultExtended")
+ .HasColumnType("boolean");
+
+ 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("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("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("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("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.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")
+ .HasColumnType("text");
+
+ b.Property("InstalledAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .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("Value")
+ .HasColumnType("real");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("DefinitionId");
+
+ 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("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("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("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("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