diff --git a/src/DataModel/Configuration/Skill.cs b/src/DataModel/Configuration/Skill.cs
index 054e7cb81..af9053749 100644
--- a/src/DataModel/Configuration/Skill.cs
+++ b/src/DataModel/Configuration/Skill.cs
@@ -308,6 +308,11 @@ public partial class Skill
[MemberOfAggregate]
public virtual AreaSkillSettings? AreaSkillSettings { get; set; }
+ ///
+ /// Gets or sets the number of hits per attack.
+ ///
+ public short NumberOfHitsPerAttack { get; set; }
+
///
public override string? ToString()
{
diff --git a/src/GameLogic/AttackableExtensions.cs b/src/GameLogic/AttackableExtensions.cs
index 610ea54c2..abba95f01 100644
--- a/src/GameLogic/AttackableExtensions.cs
+++ b/src/GameLogic/AttackableExtensions.cs
@@ -71,8 +71,7 @@ public static async ValueTask CalculateDamageAsync(this IAttacker attac
else
{
var defenseAttribute = defender.GetDefenseAttribute(attacker);
- defense = (int)defender.Attributes[defenseAttribute];
- defense -= (int)(defense * defender.Attributes[Stats.InnovationDefDecrement]);
+ defense = (int)(defender.Attributes[defenseAttribute] * defender.Attributes[Stats.DefenseDecrement]);
if (defense < 0)
{
defense = 0;
@@ -638,14 +637,27 @@ private static bool Overrates(this IAttackable defender, IAttacker attacker)
return defender.Attributes[Stats.DefenseRatePvm] > attacker.Attributes[Stats.AttackRatePvm];
}
- private static int GetDamage(this SkillEntry skill)
+ private static int GetDamage(this SkillEntry skillEntry, IAttacker attacker)
{
- skill.ThrowNotInitializedProperty(skill.Skill is null, nameof(skill.Skill));
+ skillEntry.ThrowNotInitializedProperty(skillEntry.Skill is null, nameof(skillEntry.Skill));
+ var skill = skillEntry.Skill;
- var result = skill.Skill.AttackDamage;
- if (skill.Skill.MasterDefinition != null)
+ var result = skill.AttackDamage;
+ if (attacker is Player { } player && skill.MasterDefinition is { } masterDefinition)
{
- result += (int)skill.CalculateValue();
+ if (masterDefinition.TargetAttribute is null)
+ {
+ result += (int)skillEntry.CalculateValue();
+ }
+
+ foreach (var masterSkill in skill.GetBaseSkills(true))
+ {
+ if (masterSkill.MasterDefinition!.TargetAttribute is null
+ && player.SkillList!.GetSkill((ushort)masterSkill.Number) is { } masterSkillEntry)
+ {
+ result += (int)masterSkillEntry.CalculateValue();
+ }
+ }
}
return result;
@@ -698,7 +710,7 @@ private static void GetSkillDmg(this IAttacker attacker, SkillEntry? skillEntry,
}
damageType = skill.DamageType;
- var skillDamage = skillEntry.GetDamage();
+ var skillDamage = skillEntry.GetDamage(attacker);
skillMinimumDamage += skillDamage;
skillMaximumDamage += skillDamage + (skillDamage / 2);
diff --git a/src/GameLogic/Attributes/MonsterAttributeHolder.cs b/src/GameLogic/Attributes/MonsterAttributeHolder.cs
index 7eb2ad332..45b88bc2d 100644
--- a/src/GameLogic/Attributes/MonsterAttributeHolder.cs
+++ b/src/GameLogic/Attributes/MonsterAttributeHolder.cs
@@ -22,6 +22,7 @@ public class MonsterAttributeHolder : IAttributeSystem
{ Stats.DamageReceiveDecrement, m => 1.0f },
{ Stats.AttackDamageIncrease, m => 1.0f },
{ Stats.ShieldBypassChance, m => 1.0f },
+ { Stats.DefenseDecrement, m => 1.0f - m.Attributes.GetValueOfAttribute(Stats.InnovationDefDecrement) },
};
private static readonly IDictionary> SetterMapping =
diff --git a/src/GameLogic/Attributes/Stats.cs b/src/GameLogic/Attributes/Stats.cs
index 0d2e0bded..a1a541dbc 100644
--- a/src/GameLogic/Attributes/Stats.cs
+++ b/src/GameLogic/Attributes/Stats.cs
@@ -737,7 +737,7 @@ public class Stats
/// values include:
/// .
/// values include:
- /// Greater defense buff; MST bonus defense with shield (shield strengthener); MST dark horse strengthener; Jack O'Lantern Cry bonus (halved).
+ /// Greater defense buff; MST bonus defense with shield (shield strengthener); MST dark horse strengthener; Jack O'Lantern Cry bonus (halved); Berserker defense reduction.
///
public static AttributeDefinition DefenseFinal { get; } = new(new Guid("0888AD48-0CC8-47CA-B6A3-99F3771AA5FC"), "Final Defense", string.Empty);
@@ -747,10 +747,6 @@ public class Stats
///
/// values include:
/// .
- /// values include:
- /// Fire slash defense reduction.
- /// values include:
- /// Berserker defense reduction.
///
public static AttributeDefinition DefensePvm { get; } = new(new Guid("B4201610-2824-4EC1-A145-76B15DB9DEC6"), "Defense (PvM)", string.Empty);
@@ -760,10 +756,6 @@ public class Stats
///
/// values include:
/// ; pants guardian option (halved).
- /// values include:
- /// Fire slash defense reduction.
- /// values include:
- /// Berserker defense reduction.
///
public static AttributeDefinition DefensePvp { get; } = new(new Guid("28D14EB7-1049-45BE-A7B7-D5E28E63943B"), "Defense (PvP)", string.Empty);
@@ -781,7 +773,7 @@ public class Stats
/// values include:
/// Complete set bonus multiplier (+10%); excellent DR option; socket DR option; MST PvM defense rate increase.
/// values include:
- /// MST bonus defense rate with shield (shield mastery).
+ /// MST bonus defense rate with shield (shield mastery); Phoenix Shot decrease block effect.
///
public static AttributeDefinition DefenseRatePvm { get; } = new(new Guid("C520DD2D-1B06-4392-95EE-3C41F33E68DA"), "Defense Rate (PvM)", string.Empty);
@@ -925,6 +917,16 @@ public class Stats
///
public static AttributeDefinition InnovationDefDecrement { get; } = new(new Guid("D8B3B1C9-B409-4A07-8F4D-8F315DCB173A"), "Innovation Defense Decrement", "The defense decrement due to the magic effect of innovation skill, which is multiplied with the final defense and subtracted from it.");
+ ///
+ /// Gets the defense decrement attribute definition.
+ ///
+ ///
+ /// Includes the multiplier , and the ones from the magic effects of beast uppercut (RF) and fire slash (MG).
+ /// Beast uppercut and fire slash share the same magic effect number, while innovation's is different.
+ /// This means that the first two effects can only exist 1 at a time, while innovation can coexist with either of them.
+ ///
+ public static AttributeDefinition DefenseDecrement { get; } = new(new Guid("D19A0E33-5C9A-4B8E-AF12-3C4D5E6F7890"), "Defense Decrement", "The defense decrement due to magic effects of various skills, which is multiplied with the final defense and subtracted from it.");
+
///
/// Gets the 'is shield equipped' attribute definition.
///
diff --git a/src/GameLogic/DamageAttributes.cs b/src/GameLogic/DamageAttributes.cs
index f4b8b3ac7..9b2f1e7e9 100644
--- a/src/GameLogic/DamageAttributes.cs
+++ b/src/GameLogic/DamageAttributes.cs
@@ -54,4 +54,14 @@ public enum DamageAttributes
/// The damage includes the combo bonus.
///
Combo = 128,
+
+ ///
+ /// The damage is a non-final hit of a quick sequence from a rage fighter skill.
+ ///
+ RageFighterStreakHit = 256,
+
+ ///
+ /// The damage is the final hit of a quick sequence from a rage fighter skill.
+ ///
+ RageFighterStreakFinalHit = 512,
}
\ No newline at end of file
diff --git a/src/GameLogic/IAttackable.cs b/src/GameLogic/IAttackable.cs
index 0196b9044..0d4a781f7 100644
--- a/src/GameLogic/IAttackable.cs
+++ b/src/GameLogic/IAttackable.cs
@@ -55,8 +55,13 @@ public interface IAttackable : IIdentifiable, ILocateable
/// The skill.
/// If set to true, the attacker did a combination of skills.
/// The damage factor.
+ ///
+ /// Not null when it's a rage fighter multiple hit skill:
+ /// true, if it's the final hit;
+ /// false, for other hits.
+ ///
/// Returns information about the damage inflicted.
- ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0);
+ ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null);
///
/// Reflects the damage which was done previously with or even to the .
diff --git a/src/GameLogic/NPC/AttackableNpcBase.cs b/src/GameLogic/NPC/AttackableNpcBase.cs
index 41b576586..da2a70fb6 100644
--- a/src/GameLogic/NPC/AttackableNpcBase.cs
+++ b/src/GameLogic/NPC/AttackableNpcBase.cs
@@ -64,7 +64,7 @@ protected AttackableNpcBase(MonsterSpawnArea spawnInfo, MonsterDefinition stats,
///
/// true if teleporting; otherwise, false.
///
- /// Teleporting for monsters oor npcs is not implemented yet.
+ /// Teleporting for monsters or npcs is not implemented yet.
public bool IsTeleporting => false;
///
@@ -98,7 +98,7 @@ public int Health
|| (this.SpawnArea.SpawnTrigger == SpawnTrigger.AutomaticDuringWave && (this._eventStateProvider?.IsSpawnWaveActive(this.SpawnArea.WaveNumber) ?? false));
///
- public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0)
+ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null)
{
if (this.Definition.ObjectKind == NpcObjectKind.Guard)
{
@@ -112,7 +112,7 @@ public int Health
attacker.ApplyAmmunitionConsumption(hitInfo);
}
- await this.HitAsync(hitInfo, attacker, skill?.Skill).ConfigureAwait(false);
+ await this.HitAsync(hitInfo, attacker, skill?.Skill, isFinalStreakHit).ConfigureAwait(false);
if (hitInfo.HealthDamage > 0)
{
@@ -181,7 +181,12 @@ protected virtual void OnRemoveFromMap()
/// The hit information.
/// The attacker.
/// The skill.
- protected async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? skill)
+ ///
+ /// Not null when it's a rage fighter multiple hit skill:
+ /// true, if it's the final hit;
+ /// false, for other hits.
+ ///
+ protected async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? skill, bool? isFinalStreakHit = null)
{
if (!this.IsAlive)
{
@@ -193,6 +198,16 @@ protected async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? s
var player = this.GetHitNotificationTarget(attacker);
if (player is not null)
{
+ if (isFinalStreakHit.HasValue)
+ {
+ hitInfo.Attributes |= DamageAttributes.RageFighterStreakHit;
+
+ if (isFinalStreakHit.Value || killed)
+ {
+ hitInfo.Attributes |= DamageAttributes.RageFighterStreakFinalHit;
+ }
+ }
+
await player.InvokeViewPlugInAsync(p => p.ShowHitAsync(this, hitInfo)).ConfigureAwait(false);
player.GameContext.PlugInManager.GetPlugInPoint()?.AttackableGotHit(this, attacker, hitInfo);
}
diff --git a/src/GameLogic/NPC/SoccerBall.cs b/src/GameLogic/NPC/SoccerBall.cs
index 800b87354..c207b912b 100644
--- a/src/GameLogic/NPC/SoccerBall.cs
+++ b/src/GameLogic/NPC/SoccerBall.cs
@@ -55,7 +55,7 @@ public SoccerBall(MonsterSpawnArea spawnInfo, MonsterDefinition stats, GameMap m
public DeathInformation? LastDeath => null;
///
- public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0)
+ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null)
{
var direction = attacker.GetDirectionTo(this);
await this.MoveToDirectionAsync(direction, skill is { }).ConfigureAwait(false);
diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs
index 4cda5d326..d38609f1b 100644
--- a/src/GameLogic/Player.cs
+++ b/src/GameLogic/Player.cs
@@ -675,7 +675,7 @@ public ValueTask ShowBlueMessageAsync(string message)
}
///
- public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0)
+ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null)
{
if (this.Attributes is null)
{
@@ -722,7 +722,7 @@ public ValueTask ShowBlueMessageAsync(string message)
this.Attributes[Stats.CurrentMana] = (manaFullyRecovered ? this.Attributes[Stats.MaximumMana] : this.Attributes[Stats.CurrentMana]) - hitInfo.ManaToll;
}
- await this.HitAsync(hitInfo, attacker, skill?.Skill).ConfigureAwait(false);
+ await this.HitAsync(hitInfo, attacker, skill?.Skill, isFinalStreakHit).ConfigureAwait(false);
await this.DecreaseItemDurabilityAfterHitAsync(hitInfo).ConfigureAwait(false);
if (attacker as IPlayerSurrogate is { } playerSurrogate)
@@ -1610,8 +1610,8 @@ IElement AppedMasterSkillPowerUp(SkillEntry masterSkillEntry, PowerUpDefinition
return powerUp;
}
- if (masterSkillDefinition.TargetAttribute is not { } masterSkillTargetAttribute
- || masterSkillTargetAttribute == powerUpDef.TargetAttribute)
+ if (masterSkillDefinition.TargetAttribute is { } masterSkillTargetAttribute
+ && masterSkillTargetAttribute == powerUpDef.TargetAttribute)
{
var additionalValue = new SimpleElement(masterSkillEntry.CalculateValue(), masterSkillEntry.Skill.MasterDefinition?.Aggregation ?? powerUp.AggregateType);
powerUp = new CombinedElement(powerUp, additionalValue);
@@ -2085,7 +2085,7 @@ private async ValueTask GetSpawnGateOfCurrentMapAsync()
?? throw new InvalidOperationException($"Game map {spawnTargetMapDefinition} has no spawn gate.");
}
- private async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? skill)
+ private async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? skill, bool? isFinalStreakHit = null)
{
this.Summon?.Item2.RegisterHit(attacker);
var healthDamage = hitInfo.HealthDamage;
@@ -2101,6 +2101,17 @@ private async ValueTask HitAsync(HitInfo hitInfo, IAttacker attacker, Skill? ski
}
this.Attributes[Stats.CurrentHealth] -= healthDamage;
+
+ if (isFinalStreakHit.HasValue)
+ {
+ hitInfo.Attributes |= DamageAttributes.RageFighterStreakHit;
+
+ if (isFinalStreakHit.Value || this.Attributes[Stats.CurrentHealth] < 1)
+ {
+ hitInfo.Attributes |= DamageAttributes.RageFighterStreakFinalHit;
+ }
+ }
+
await this.InvokeViewPlugInAsync(p => p.ShowHitAsync(this, hitInfo)).ConfigureAwait(false);
if (attacker is IWorldObserver observer)
{
diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
index fb39be878..6296787e2 100644
--- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
+++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs
@@ -320,15 +320,22 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar
private async ValueTask ApplySkillAsync(Player player, SkillEntry skillEntry, IAttackable target, Point targetAreaCenter, bool isCombo)
{
skillEntry.ThrowNotInitializedProperty(skillEntry.Skill is null, nameof(skillEntry.Skill));
+ var skill = skillEntry.Skill;
- if (skillEntry.Skill.SkillType == SkillType.Buff)
+ if (skill.SkillType == SkillType.Buff)
{
await target.ApplyMagicEffectAsync(player, skillEntry).ConfigureAwait(false);
return;
}
- var hitInfo = await target.AttackByAsync(player, skillEntry, isCombo).ConfigureAwait(false);
+ var hitInfo = await target.AttackByAsync(player, skillEntry, isCombo, 1, skill.NumberOfHitsPerAttack > 1 ? false : null).ConfigureAwait(false);
await target.TryApplyElementalEffectsAsync(player, skillEntry).ConfigureAwait(false);
+
+ for (int hit = 2; hit <= skill.NumberOfHitsPerAttack; hit++)
+ {
+ await target.AttackByAsync(player, skillEntry, isCombo, 1, hit == skill.NumberOfHitsPerAttack).ConfigureAwait(false);
+ }
+
var baseSkill = skillEntry.GetBaseSkill();
if (player.GameContext.PlugInManager.GetStrategy(baseSkill.Number) is { } strategy)
diff --git a/src/GameLogic/PlayerActions/Skills/DragonRoarSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/DragonRoarSkillPlugIn.cs
new file mode 100644
index 000000000..5493e23f4
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Skills/DragonRoarSkillPlugIn.cs
@@ -0,0 +1,94 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.GameLogic.GuildWar;
+using MUnique.OpenMU.GameLogic.NPC;
+using MUnique.OpenMU.GameLogic.PlugIns;
+using MUnique.OpenMU.Pathfinding;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// Handles the dragon roar skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets.
+///
+[PlugIn]
+[Display(Name = nameof(PlugInResources.DragonRoarSkillPlugIn_Name), Description = nameof(PlugInResources.DragonRoarSkillPlugIn_Description), ResourceType = typeof(PlugInResources))]
+[Guid("A797A6AD-AC92-4731-A0FB-D46D4C1DD0DF")]
+public class DragonRoarSkillPlugIn : IAreaSkillPlugIn
+{
+ ///
+ public virtual short Key => 264;
+
+ ///
+ /// Gets the range around the target, in which additional targets are searched.
+ ///
+ protected virtual short Range => 3;
+
+ ///
+ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo)
+ {
+ bool FilterTarget(IAttackable attackable)
+ {
+ if (attackable == target)
+ {
+ return false;
+ }
+
+ if (attackable is Monster { SummonedBy: null } or Destructible)
+ {
+ return true;
+ }
+
+ if (attackable is Monster { SummonedBy: not null } summoned)
+ {
+ return FilterTarget(summoned.SummonedBy);
+ }
+
+ if (attackable is Player { DuelRoom.State: DuelState.DuelStarted } targetPlayer
+ && attacker is Player { DuelRoom.State: DuelState.DuelStarted } duelPlayer
+ && targetPlayer.DuelRoom == duelPlayer.DuelRoom
+ && targetPlayer.DuelRoom.IsDuelist(targetPlayer) && targetPlayer.DuelRoom.IsDuelist(duelPlayer))
+ {
+ return true;
+ }
+
+ if (attackable is Player { GuildWarContext.State: GuildWarState.Started } guildWarTarget
+ && attacker is Player { GuildWarContext.State: GuildWarState.Started } guildWarAttacker
+ && guildWarTarget.GuildWarContext == guildWarAttacker.GuildWarContext)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ var extraTargets = target.CurrentMap?.GetAttackablesInRange(target.Position, this.Range).Where(FilterTarget).Take(7);
+
+ if (extraTargets is null)
+ {
+ return;
+ }
+
+ int i = 1;
+ var skill = skillEntry.Skill!;
+ foreach (var extraTarget in extraTargets)
+ {
+ if (i <= 3 || Rand.NextRandomBool())
+ {
+ // first three 100% chance, others 50% chance
+ await extraTarget.AttackByAsync(attacker, skillEntry, false, 1, false).ConfigureAwait(false);
+ await extraTarget.TryApplyElementalEffectsAsync(attacker, skillEntry).ConfigureAwait(false);
+
+ for (int hit = 2; hit <= skill.NumberOfHitsPerAttack; hit++)
+ {
+ await extraTarget.AttackByAsync(attacker, skillEntry, false, 1, hit == skill.NumberOfHitsPerAttack).ConfigureAwait(false);
+ }
+ }
+
+ i++;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Skills/PhoenixShotSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/PhoenixShotSkillPlugIn.cs
new file mode 100644
index 000000000..b26ffe307
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Skills/PhoenixShotSkillPlugIn.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.PlayerActions.Skills;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// Handles the phoenix shot weapon skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets.
+///
+[PlugIn]
+[Display(Name = nameof(PlugInResources.PhoenixShotSkillPlugIn_Name), Description = nameof(PlugInResources.PhoenixShotSkillPlugIn_Description), ResourceType = typeof(PlugInResources))]
+[Guid("2C78E3DB-DDC7-4BC6-8539-707F81638ABF")]
+public class PhoenixShotSkillPlugIn : DragonRoarSkillPlugIn
+{
+ ///
+ public override short Key => 270;
+
+ ///
+ protected override short Range => 2;
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Skills/TargetedSkillDefaultPlugin.cs b/src/GameLogic/PlayerActions/Skills/TargetedSkillDefaultPlugin.cs
index e6f0f4f4e..a725b3b98 100644
--- a/src/GameLogic/PlayerActions/Skills/TargetedSkillDefaultPlugin.cs
+++ b/src/GameLogic/PlayerActions/Skills/TargetedSkillDefaultPlugin.cs
@@ -263,9 +263,14 @@ private async ValueTask ApplySkillAsync(Player player, IAttackable targete
if (!target.IsAtSafezone() && !player.IsAtSafezone() && target != player)
{
- await target.AttackByAsync(player, skillEntry, isCombo).ConfigureAwait(false);
+ await target.AttackByAsync(player, skillEntry, isCombo, 1, skill.NumberOfHitsPerAttack > 1 ? false : null).ConfigureAwait(false);
player.LastAttackedTarget.SetTarget(target);
success = await target.TryApplyElementalEffectsAsync(player, skillEntry).ConfigureAwait(false) || success;
+
+ for (int hit = 2; hit <= skill.NumberOfHitsPerAttack; hit++)
+ {
+ await target.AttackByAsync(player, skillEntry, isCombo, 1, hit == skill.NumberOfHitsPerAttack).ConfigureAwait(false);
+ }
}
}
else if (skill.MagicEffectDef != null)
diff --git a/src/GameLogic/Properties/PlugInResources.Designer.cs b/src/GameLogic/Properties/PlugInResources.Designer.cs
index 4ae51954c..258a76f79 100644
--- a/src/GameLogic/Properties/PlugInResources.Designer.cs
+++ b/src/GameLogic/Properties/PlugInResources.Designer.cs
@@ -519,6 +519,24 @@ public static string DisconnectChatCommandPlugIn_Name {
}
}
+ ///
+ /// Looks up a localized string similar to Handles the dragon roar skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets..
+ ///
+ public static string DragonRoarSkillPlugIn_Description {
+ get {
+ return ResourceManager.GetString("DragonRoarSkillPlugIn_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Dragon Roar Skill.
+ ///
+ public static string DragonRoarSkillPlugIn_Name {
+ get {
+ return ResourceManager.GetString("DragonRoarSkillPlugIn_Name", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Handles the drain life skill of the summoner class. Additionally to the attacked target, it regains life for damage dealt..
///
@@ -1617,6 +1635,24 @@ public static string PeriodicTaskConfiguration_Timetable_Name {
}
}
+ ///
+ /// Looks up a localized string similar to Handles the phoenix shot weapon skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets..
+ ///
+ public static string PhoenixShotSkillPlugIn_Description {
+ get {
+ return ResourceManager.GetString("PhoenixShotSkillPlugIn_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Phoenix Shot Skill.
+ ///
+ public static string PhoenixShotSkillPlugIn_Name {
+ get {
+ return ResourceManager.GetString("PhoenixShotSkillPlugIn_Name", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Handles the chat command '/pk <char> <pk_lvl> <pk_count>'. Sets player kill level and count of a character..
///
diff --git a/src/GameLogic/Properties/PlugInResources.resx b/src/GameLogic/Properties/PlugInResources.resx
index cd9958fe4..4e828642e 100644
--- a/src/GameLogic/Properties/PlugInResources.resx
+++ b/src/GameLogic/Properties/PlugInResources.resx
@@ -369,6 +369,12 @@
Handles the chain lightning skill of the summoner class. Additionally to the attacked target, it will hit up to two additional targets.
+
+ Dragon Roar Skill
+
+
+ Handles the dragon roar skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets.
+
Drain Life Skill
@@ -405,6 +411,12 @@
Handles the stopping of nova skill of the wizard class.
+
+ Phoenix Shot Skill
+
+
+ Handles the phoenix shot weapon skill of the rage fighter class. Additionally to the attacked target, it will hit up to seven additional targets.
+
Plasma Storm Skill
diff --git a/src/GameServer/MessageHandler/RageSkillAttackHandlerPlugIn.cs b/src/GameServer/MessageHandler/RageSkillAttackHandlerPlugIn.cs
index 5c4073f43..35f735a3d 100644
--- a/src/GameServer/MessageHandler/RageSkillAttackHandlerPlugIn.cs
+++ b/src/GameServer/MessageHandler/RageSkillAttackHandlerPlugIn.cs
@@ -13,7 +13,7 @@ namespace MUnique.OpenMU.GameServer.MessageHandler;
using MUnique.OpenMU.PlugIns;
///
-/// Handler for rage fighter area skill attack packets (beast uppercut, chain drive, dark side).
+/// Handler for rage fighter area skill attack packets (beast uppercut, chain drive, dragon slasher, dark side).
///
[PlugIn]
[Display(Name = nameof(PlugInResources.RageSkillAttackHandlerPlugIn_Name), Description = nameof(PlugInResources.RageSkillAttackHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
@@ -36,7 +36,7 @@ public async ValueTask HandlePacketAsync(Player player, Memory packet)
var target = player.GetObject(targetId) as IAttackable;
if (player.SkillList is null || player.SkillList.GetSkill(message.SkillId) is not { } skill
- || (target is null || !target.IsInRange(player.Position, skill.Skill!.Range)))
+ || target is null || !target.IsInRange(player.Position, skill.Skill!.Range))
{
return;
}
diff --git a/src/GameServer/RemoteView/Character/SkillListViewPlugIn.cs b/src/GameServer/RemoteView/Character/SkillListViewPlugIn.cs
index f214e3756..9460b65c1 100644
--- a/src/GameServer/RemoteView/Character/SkillListViewPlugIn.cs
+++ b/src/GameServer/RemoteView/Character/SkillListViewPlugIn.cs
@@ -26,6 +26,12 @@ public class SkillListViewPlugIn : ISkillListViewPlugIn
private const short ForceSkillId = 60;
private const short ForceWaveSkillId = 66;
private const short ForceWaveStrengSkillId = 509;
+ private const short KillingBlowSkillId = 260;
+ private const short BeastUppercutSkillId = 261;
+ private const short KillingBlowStrengSkillId = 551;
+ private const short BeastUppercutStrengSkillId = 552;
+ private const short KillingBlowMasterySkillId = 554;
+ private const short BeastUppercutMasterySkillId = 555;
private readonly RemotePlayer _player;
@@ -61,6 +67,18 @@ public virtual async ValueTask AddSkillAsync(Skill skill)
return;
}
+ if (skill.Number == KillingBlowSkillId
+ && this.SkillList.Any(s => s?.Number == KillingBlowStrengSkillId || s?.Number == KillingBlowMasterySkillId))
+ {
+ return;
+ }
+
+ if (skill.Number == BeastUppercutSkillId
+ && this.SkillList.Any(s => s?.Number == BeastUppercutStrengSkillId || s?.Number == BeastUppercutMasterySkillId))
+ {
+ return;
+ }
+
var skillIndex = this.AddSkillToList(skill);
await this._player.Connection.SendSkillAddedAsync(skillIndex, (ushort)skill.Number, 0).ConfigureAwait(false);
}
diff --git a/src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs b/src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs
index 5f44b6704..bedda8947 100644
--- a/src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs
+++ b/src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs
@@ -53,6 +53,8 @@ public async ValueTask ShowHitAsync(IAttackable target, HitInfo hitInfo)
var targetId = target.GetId(this._player);
await connection.SendObjectHitExtendedAsync(
this.GetDamageKind(hitInfo.Attributes),
+ hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakHit),
+ hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakFinalHit),
hitInfo.Attributes.HasFlag(DamageAttributes.Double),
hitInfo.Attributes.HasFlag(DamageAttributes.Triple),
targetId,
@@ -99,7 +101,7 @@ private DamageKind GetDamageKind(DamageAttributes attributes)
if (attributes.HasFlag(DamageAttributes.Reflected))
{
- return DamageKind.ReflectedDarkPink;
+ return DamageKind.ReflectedLightPink;
}
if (attributes.HasFlag(DamageAttributes.Poison))
diff --git a/src/GameServer/RemoteView/World/ShowHitPlugIn.cs b/src/GameServer/RemoteView/World/ShowHitPlugIn.cs
index c304b49a3..d82dd219e 100644
--- a/src/GameServer/RemoteView/World/ShowHitPlugIn.cs
+++ b/src/GameServer/RemoteView/World/ShowHitPlugIn.cs
@@ -63,6 +63,8 @@ await connection.SendObjectHitAsync(
targetId,
healthDamage,
this.GetDamageKind(hitInfo.Attributes),
+ hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakHit),
+ hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakFinalHit),
hitInfo.Attributes.HasFlag(DamageAttributes.Double),
hitInfo.Attributes.HasFlag(DamageAttributes.Triple),
shieldDamage).ConfigureAwait(false);
@@ -119,7 +121,7 @@ private DamageKind GetDamageKind(DamageAttributes attributes)
if (attributes.HasFlag(DamageAttributes.Reflected))
{
- return DamageKind.ReflectedDarkPink;
+ return DamageKind.ReflectedLightPink;
}
if (attributes.HasFlag(DamageAttributes.Poison))
diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
index 9f3e850b9..fefddfa0c 100644
--- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
+++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
@@ -1049,6 +1049,8 @@ int WritePacket()
/// The object id.
/// The health damage.
/// The kind.
+ /// The is rage fighter streak hit.
+ /// The is rage fighter streak final hit.
/// The is double damage.
/// The is triple damage.
/// The shield damage.
@@ -1056,7 +1058,7 @@ int WritePacket()
/// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit.
/// Causes reaction on client side: The damage is shown at the object which received the hit.
///
- public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage)
+ public static async ValueTask SendObjectHitAsync(this IConnection? connection, byte @headerCode, ushort @objectId, ushort @healthDamage, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @shieldDamage)
{
if (connection is null)
{
@@ -1071,6 +1073,8 @@ int WritePacket()
packet.ObjectId = @objectId;
packet.HealthDamage = @healthDamage;
packet.Kind = @kind;
+ packet.IsRageFighterStreakHit = @isRageFighterStreakHit;
+ packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit;
packet.IsDoubleDamage = @isDoubleDamage;
packet.IsTripleDamage = @isTripleDamage;
packet.ShieldDamage = @shieldDamage;
@@ -1086,6 +1090,8 @@ int WritePacket()
///
/// The connection.
/// The kind.
+ /// The is rage fighter streak hit.
+ /// The is rage fighter streak final hit.
/// The is double damage.
/// The is triple damage.
/// The object id.
@@ -1097,7 +1103,7 @@ int WritePacket()
/// Is sent by the server when: An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit.
/// Causes reaction on client side: The damage is shown at the object which received the hit.
///
- public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage)
+ public static async ValueTask SendObjectHitExtendedAsync(this IConnection? connection, DamageKind @kind, bool @isRageFighterStreakHit, bool @isRageFighterStreakFinalHit, bool @isDoubleDamage, bool @isTripleDamage, ushort @objectId, byte @healthStatus, byte @shieldStatus, uint @healthDamage, uint @shieldDamage)
{
if (connection is null)
{
@@ -1109,6 +1115,8 @@ int WritePacket()
var length = ObjectHitExtendedRef.Length;
var packet = new ObjectHitExtendedRef(connection.Output.GetSpan(length)[..length]);
packet.Kind = @kind;
+ packet.IsRageFighterStreakHit = @isRageFighterStreakHit;
+ packet.IsRageFighterStreakFinalHit = @isRageFighterStreakFinalHit;
packet.IsDoubleDamage = @isDoubleDamage;
packet.IsTripleDamage = @isTripleDamage;
packet.ObjectId = @objectId;
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
index 6c5980ab5..d36adccad 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
@@ -6464,6 +6464,24 @@ public DamageKind Kind
set => this._data.Span[7..].SetByteValue((byte)value, 4, 0);
}
+ ///
+ /// Gets or sets the is rage fighter streak hit.
+ ///
+ public bool IsRageFighterStreakHit
+ {
+ get => this._data.Span[7..].GetBoolean(4);
+ set => this._data.Span[7..].SetBoolean(value, 4);
+ }
+
+ ///
+ /// Gets or sets the is rage fighter streak final hit.
+ ///
+ public bool IsRageFighterStreakFinalHit
+ {
+ get => this._data.Span[7..].GetBoolean(5);
+ set => this._data.Span[7..].SetBoolean(value, 5);
+ }
+
///
/// Gets or sets the is double damage.
///
@@ -6570,6 +6588,24 @@ public DamageKind Kind
set => this._data.Span[3..].SetByteValue((byte)value, 4, 0);
}
+ ///
+ /// Gets or sets the is rage fighter streak hit.
+ ///
+ public bool IsRageFighterStreakHit
+ {
+ get => this._data.Span[3..].GetBoolean(4);
+ set => this._data.Span[3..].SetBoolean(value, 4);
+ }
+
+ ///
+ /// Gets or sets the is rage fighter streak final hit.
+ ///
+ public bool IsRageFighterStreakFinalHit
+ {
+ get => this._data.Span[3..].GetBoolean(5);
+ set => this._data.Span[3..].SetBoolean(value, 5);
+ }
+
///
/// Gets or sets the is double damage.
///
@@ -30545,9 +30581,9 @@ public enum DamageKind
CriticalBlue = 3,
///
- /// Light pink color.
+ /// Light pink color, usually used by reflected damage.
///
- LightPink = 4,
+ ReflectedLightPink = 4,
///
/// Dark green color, usually used by poison damage.
@@ -30555,9 +30591,9 @@ public enum DamageKind
PoisonDarkGreen = 5,
///
- /// Dark pink color, usually used by reflected damage.
+ /// Dark pink color.
///
- ReflectedDarkPink = 6,
+ DarkPink = 6,
///
/// White color.
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
index d270b0d74..65e8368e7 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
@@ -2368,6 +2368,18 @@
Kind
4
+
+ 7
+ 4
+ Boolean
+ IsRageFighterStreakHit
+
+
+ 7
+ 5
+ Boolean
+ IsRageFighterStreakFinalHit
+
7
6
@@ -2404,6 +2416,18 @@
Kind
4
+
+ 3
+ 4
+ Boolean
+ IsRageFighterStreakHit
+
+
+ 3
+ 5
+ Boolean
+ IsRageFighterStreakFinalHit
+
3
6
@@ -11270,8 +11294,8 @@
3
- LightPink
- Light pink color.
+ ReflectedLightPink
+ Light pink color, usually used by reflected damage.
4
@@ -11280,8 +11304,8 @@
5
- ReflectedDarkPink
- Dark pink color, usually used by reflected damage.
+ DarkPink
+ Dark pink color.
6
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
index 08387116d..f723a70bf 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
@@ -6315,6 +6315,24 @@ public DamageKind Kind
set => this._data[7..].SetByteValue((byte)value, 4, 0);
}
+ ///
+ /// Gets or sets the is rage fighter streak hit.
+ ///
+ public bool IsRageFighterStreakHit
+ {
+ get => this._data[7..].GetBoolean(4);
+ set => this._data[7..].SetBoolean(value, 4);
+ }
+
+ ///
+ /// Gets or sets the is rage fighter streak final hit.
+ ///
+ public bool IsRageFighterStreakFinalHit
+ {
+ get => this._data[7..].GetBoolean(5);
+ set => this._data[7..].SetBoolean(value, 5);
+ }
+
///
/// Gets or sets the is double damage.
///
@@ -6421,6 +6439,24 @@ public DamageKind Kind
set => this._data[3..].SetByteValue((byte)value, 4, 0);
}
+ ///
+ /// Gets or sets the is rage fighter streak hit.
+ ///
+ public bool IsRageFighterStreakHit
+ {
+ get => this._data[3..].GetBoolean(4);
+ set => this._data[3..].SetBoolean(value, 4);
+ }
+
+ ///
+ /// Gets or sets the is rage fighter streak final hit.
+ ///
+ public bool IsRageFighterStreakFinalHit
+ {
+ get => this._data[3..].GetBoolean(5);
+ set => this._data[3..].SetBoolean(value, 5);
+ }
+
///
/// Gets or sets the is double damage.
///
diff --git a/src/Persistence/EntityFramework/Migrations/20251217154940_AddSkillNumberOfHitsPerAttack.Designer.cs b/src/Persistence/EntityFramework/Migrations/20251217154940_AddSkillNumberOfHitsPerAttack.Designer.cs
new file mode 100644
index 000000000..98ea46d3c
--- /dev/null
+++ b/src/Persistence/EntityFramework/Migrations/20251217154940_AddSkillNumberOfHitsPerAttack.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("20251217154940_AddSkillNumberOfHitsPerAttack")]
+ partial class AddSkillNumberOfHitsPerAttack
+ {
+ ///
+ 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("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("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