diff --git a/docs/Packets/C1-26-FE-MaximumHealthAndShieldExtended_by-server.md b/docs/Packets/C1-26-FE-MaximumStatsExtended_by-server.md
similarity index 50%
rename from docs/Packets/C1-26-FE-MaximumHealthAndShieldExtended_by-server.md
rename to docs/Packets/C1-26-FE-MaximumStatsExtended_by-server.md
index 9ea3b588d..789acc585 100644
--- a/docs/Packets/C1-26-FE-MaximumHealthAndShieldExtended_by-server.md
+++ b/docs/Packets/C1-26-FE-MaximumStatsExtended_by-server.md
@@ -1,20 +1,22 @@
-# C1 26 FE - MaximumHealthAndShieldExtended (by server)
+# C1 26 FE - MaximumStatsExtended (by server)
## Is sent when
-When the maximum health changed, e.g. by adding stat points or changed items.
+When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items.
## Causes the following actions on the client side
-The health and shield bar is updated on the game client user interface.
+The values are updated on the game client user interface.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
-| 1 | 1 | Byte | 12 | Packet header - length of the packet |
+| 1 | 1 | Byte | 20 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x26 | Packet header - packet type identifier |
| 3 | 1 | Byte | 0xFE | Packet header - sub packet type identifier |
| 4 | 4 | IntegerLittleEndian | | Health |
-| 8 | 4 | IntegerLittleEndian | | Shield |
\ No newline at end of file
+| 8 | 4 | IntegerLittleEndian | | Shield |
+| 12 | 4 | IntegerLittleEndian | | Mana |
+| 16 | 4 | IntegerLittleEndian | | Ability |
\ No newline at end of file
diff --git a/docs/Packets/C1-26-FF-CurrentHealthAndShieldExtended_by-server.md b/docs/Packets/C1-26-FF-CurrentHealthAndShieldExtended_by-server.md
deleted file mode 100644
index 72edaaf40..000000000
--- a/docs/Packets/C1-26-FF-CurrentHealthAndShieldExtended_by-server.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# C1 26 FF - CurrentHealthAndShieldExtended (by server)
-
-## Is sent when
-
-Periodically, or if the current health or shield changed on the server side, e.g. by hits.
-
-## Causes the following actions on the client side
-
-The health and shield bar is updated on the game client user interface.
-
-## Structure
-
-| Index | Length | Data Type | Value | Description |
-|-------|--------|-----------|-------|-------------|
-| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
-| 1 | 1 | Byte | 12 | Packet header - length of the packet |
-| 2 | 1 | Byte | 0x26 | Packet header - packet type identifier |
-| 3 | 1 | Byte | 0xFF | Packet header - sub packet type identifier |
-| 4 | 4 | IntegerLittleEndian | | Health |
-| 8 | 4 | IntegerLittleEndian | | Shield |
\ No newline at end of file
diff --git a/docs/Packets/C1-26-FF-CurrentStatsExtended_by-server.md b/docs/Packets/C1-26-FF-CurrentStatsExtended_by-server.md
new file mode 100644
index 000000000..a979a7139
--- /dev/null
+++ b/docs/Packets/C1-26-FF-CurrentStatsExtended_by-server.md
@@ -0,0 +1,24 @@
+# C1 26 FF - CurrentStatsExtended (by server)
+
+## Is sent when
+
+Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits.
+
+## Causes the following actions on the client side
+
+The values are updated on the game client user interface.
+
+## Structure
+
+| Index | Length | Data Type | Value | Description |
+|-------|--------|-----------|-------|-------------|
+| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
+| 1 | 1 | Byte | 24 | Packet header - length of the packet |
+| 2 | 1 | Byte | 0x26 | Packet header - packet type identifier |
+| 3 | 1 | Byte | 0xFF | Packet header - sub packet type identifier |
+| 4 | 4 | IntegerLittleEndian | | Health |
+| 8 | 4 | IntegerLittleEndian | | Shield |
+| 12 | 4 | IntegerLittleEndian | | Mana |
+| 16 | 4 | IntegerLittleEndian | | Ability |
+| 20 | 2 | ShortLittleEndian | | AttackSpeed |
+| 22 | 2 | ShortLittleEndian | | MagicSpeed |
\ No newline at end of file
diff --git a/docs/Packets/C1-27-FE-MaximumManaAndAbilityExtended_by-server.md b/docs/Packets/C1-27-FE-MaximumManaAndAbilityExtended_by-server.md
deleted file mode 100644
index 360ea9520..000000000
--- a/docs/Packets/C1-27-FE-MaximumManaAndAbilityExtended_by-server.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# C1 27 FE - MaximumManaAndAbilityExtended (by server)
-
-## Is sent when
-
-The maximum available mana or ability has changed, e.g. by adding stat points.
-
-## Causes the following actions on the client side
-
-The mana and ability bar is updated on the game client user interface.
-
-## Structure
-
-| Index | Length | Data Type | Value | Description |
-|-------|--------|-----------|-------|-------------|
-| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
-| 1 | 1 | Byte | 12 | Packet header - length of the packet |
-| 2 | 1 | Byte | 0x27 | Packet header - packet type identifier |
-| 3 | 1 | Byte | 0xFE | Packet header - sub packet type identifier |
-| 4 | 4 | IntegerLittleEndian | | Mana |
-| 8 | 4 | IntegerLittleEndian | | Ability |
\ No newline at end of file
diff --git a/docs/Packets/C1-27-FF-CurrentManaAndAbilityExtended_by-server.md b/docs/Packets/C1-27-FF-CurrentManaAndAbilityExtended_by-server.md
deleted file mode 100644
index cafa844ab..000000000
--- a/docs/Packets/C1-27-FF-CurrentManaAndAbilityExtended_by-server.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# C1 27 FF - CurrentManaAndAbilityExtended (by server)
-
-## Is sent when
-
-The currently available mana or ability has changed, e.g. by using a skill.
-
-## Causes the following actions on the client side
-
-The mana and ability bar is updated on the game client user interface.
-
-## Structure
-
-| Index | Length | Data Type | Value | Description |
-|-------|--------|-----------|-------|-------------|
-| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
-| 1 | 1 | Byte | 12 | Packet header - length of the packet |
-| 2 | 1 | Byte | 0x27 | Packet header - packet type identifier |
-| 3 | 1 | Byte | 0xFF | Packet header - sub packet type identifier |
-| 4 | 4 | IntegerLittleEndian | | Mana |
-| 8 | 4 | IntegerLittleEndian | | Ability |
\ No newline at end of file
diff --git a/docs/Packets/C3-29-ConsumeItemWithEffect_by-server.md b/docs/Packets/C3-29-ConsumeItemWithEffect_by-server.md
index 9c34e9d60..d87d15caa 100644
--- a/docs/Packets/C3-29-ConsumeItemWithEffect_by-server.md
+++ b/docs/Packets/C3-29-ConsumeItemWithEffect_by-server.md
@@ -16,7 +16,7 @@ The player is shown in a red color and has increased attack speed.
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x29 | Packet header - packet type identifier |
| 3 | 1 | ConsumedItemType | | ItemType |
-| 4 | 2 | ShortBigEndian | | EffectTimeInSeconds |
+| 4 | 2 | ShortLittleEndian | | EffectTimeInSeconds |
### ConsumedItemType Enum
diff --git a/docs/Packets/ServerToClient.md b/docs/Packets/ServerToClient.md
index f840be18d..5d975f20f 100644
--- a/docs/Packets/ServerToClient.md
+++ b/docs/Packets/ServerToClient.md
@@ -50,13 +50,11 @@
* [C1 26 FD - ItemConsumptionFailed (by server)](C1-26-FD-ItemConsumptionFailed_by-server.md)
* [C1 26 FD - ItemConsumptionFailedExtended (by server)](C1-26-FD-ItemConsumptionFailedExtended_by-server.md)
* [C1 26 FE - MaximumHealthAndShield (by server)](C1-26-FE-MaximumHealthAndShield_by-server.md)
- * [C1 26 FE - MaximumHealthAndShieldExtended (by server)](C1-26-FE-MaximumHealthAndShieldExtended_by-server.md)
+ * [C1 26 FE - MaximumStatsExtended (by server)](C1-26-FE-MaximumStatsExtended_by-server.md)
* [C1 26 FF - CurrentHealthAndShield (by server)](C1-26-FF-CurrentHealthAndShield_by-server.md)
- * [C1 26 FF - CurrentHealthAndShieldExtended (by server)](C1-26-FF-CurrentHealthAndShieldExtended_by-server.md)
+ * [C1 26 FF - CurrentStatsExtended (by server)](C1-26-FF-CurrentStatsExtended_by-server.md)
* [C1 27 FE - MaximumManaAndAbility (by server)](C1-27-FE-MaximumManaAndAbility_by-server.md)
- * [C1 27 FE - MaximumManaAndAbilityExtended (by server)](C1-27-FE-MaximumManaAndAbilityExtended_by-server.md)
* [C1 27 FF - CurrentManaAndAbility (by server)](C1-27-FF-CurrentManaAndAbility_by-server.md)
- * [C1 27 FF - CurrentManaAndAbilityExtended (by server)](C1-27-FF-CurrentManaAndAbilityExtended_by-server.md)
* [C1 28 - ItemRemoved (by server)](C1-28-ItemRemoved_by-server.md)
* [C3 29 - ConsumeItemWithEffect (by server)](C3-29-ConsumeItemWithEffect_by-server.md)
* [C1 2A - ItemDurabilityChanged (by server)](C1-2A-ItemDurabilityChanged_by-server.md)
diff --git a/src/AttributeSystem/AttributeDefinition.cs b/src/AttributeSystem/AttributeDefinition.cs
index 5c0487a19..7f7c32039 100644
--- a/src/AttributeSystem/AttributeDefinition.cs
+++ b/src/AttributeSystem/AttributeDefinition.cs
@@ -38,19 +38,18 @@ public AttributeDefinition(Guid id, string designation, string description)
///
/// Gets or sets the designation.
///
- ///
- /// The designation.
- ///
public string? Designation { get; set; }
///
/// Gets or sets the description.
///
- ///
- /// The description.
- ///
public string? Description { get; set; }
+ ///
+ /// Gets or sets the maximum value of this attribute, if the value should be capped.
+ ///
+ public float? MaximumValue { get; set; }
+
///
/// Implements the operator ==.
///
diff --git a/src/AttributeSystem/AttributeSystem.cs b/src/AttributeSystem/AttributeSystem.cs
index d3af091e6..432e1b768 100644
--- a/src/AttributeSystem/AttributeSystem.cs
+++ b/src/AttributeSystem/AttributeSystem.cs
@@ -111,6 +111,11 @@ public float GetValueOfAttribute(AttributeDefinition? attributeDefinition)
var element = this.GetAttribute(attributeDefinition);
if (element != null)
{
+ if (attributeDefinition?.MaximumValue is { } maximumValue && element.Value > maximumValue)
+ {
+ return maximumValue;
+ }
+
return element.Value;
}
diff --git a/src/AttributeSystem/ComposableAttribute.cs b/src/AttributeSystem/ComposableAttribute.cs
index b8907c5c0..405020505 100644
--- a/src/AttributeSystem/ComposableAttribute.cs
+++ b/src/AttributeSystem/ComposableAttribute.cs
@@ -77,7 +77,13 @@ private float GetAndCacheValue()
// nothing to do
}
- this._cachedValue = (rawValues * multiValues) + finalValues;
+ var newValue = (rawValues * multiValues) + finalValues;
+ if (this.Definition.MaximumValue.HasValue)
+ {
+ newValue = Math.Min(this.Definition.MaximumValue.Value, newValue);
+ }
+
+ this._cachedValue = newValue;
return this._cachedValue.Value;
}
diff --git a/src/AttributeSystem/StatAttribute.cs b/src/AttributeSystem/StatAttribute.cs
index 3d5bd168d..4d53cdbff 100644
--- a/src/AttributeSystem/StatAttribute.cs
+++ b/src/AttributeSystem/StatAttribute.cs
@@ -38,7 +38,15 @@ public StatAttribute(AttributeDefinition definition, float baseValue)
///
public new virtual float Value
{
- get => this._statValue;
+ get
+ {
+ if (this.Definition.MaximumValue.HasValue)
+ {
+ return Math.Min(this.Definition.MaximumValue.Value, this._statValue);
+ }
+
+ return this._statValue;
+ }
set
{
diff --git a/src/GameLogic/AttackableExtensions.cs b/src/GameLogic/AttackableExtensions.cs
index 110974ad4..4de23e609 100644
--- a/src/GameLogic/AttackableExtensions.cs
+++ b/src/GameLogic/AttackableExtensions.cs
@@ -199,14 +199,13 @@ public static async ValueTask ApplyRegenerationAsync(this IAttackable target, Pl
if (target is IWorldObserver observer)
{
- if (isHealthUpdated)
- {
- await observer.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
- }
+ var updatedStats =
+ (isHealthUpdated ? IUpdateStatsPlugIn.UpdatedStats.Health : IUpdateStatsPlugIn.UpdatedStats.Undefined)
+ | (isManaUpdated ? IUpdateStatsPlugIn.UpdatedStats.Mana : IUpdateStatsPlugIn.UpdatedStats.Undefined);
- if (isManaUpdated)
+ if (updatedStats != IUpdateStatsPlugIn.UpdatedStats.Undefined)
{
- await observer.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await observer.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(updatedStats)).ConfigureAwait(false);
}
}
}
diff --git a/src/GameLogic/Attributes/Stats.cs b/src/GameLogic/Attributes/Stats.cs
index 6a28bbf6f..e9a867261 100644
--- a/src/GameLogic/Attributes/Stats.cs
+++ b/src/GameLogic/Attributes/Stats.cs
@@ -284,7 +284,38 @@ public class Stats
///
/// Gets the attack speed attribute definition.
///
- public static AttributeDefinition AttackSpeed { get; } = new (new Guid("BACC1115-1E8B-4E62-B952-8F8DDB58A949"), "Attack Speed", string.Empty);
+ public static AttributeDefinition AttackSpeed { get; } = new(new Guid("BACC1115-1E8B-4E62-B952-8F8DDB58A949"), "Attack Speed", string.Empty)
+ {
+ MaximumValue = 200,
+ };
+
+ ///
+ /// Gets the attack speed by weapon attribute definition.
+ ///
+ public static AttributeDefinition AttackSpeedByWeapon { get; } = new(new Guid("45EEEDEE-C76B-40E6-A0BC-2B493E10B140"), "Attack Speed by Weapons", string.Empty);
+
+ ///
+ /// Gets the attribute which says, if two weapons are equipped.
+ ///
+ public static AttributeDefinition AreTwoWeaponsEquipped { get; } = new(new Guid("56DA895D-BAFD-4A5C-9864-B17AB8369998"), "Are two weapons equipped", string.Empty)
+ {
+ MaximumValue = 1,
+ };
+
+ ///
+ /// Gets the attribute which counts the equipped weapons.
+ ///
+ public static AttributeDefinition EquippedWeaponCount { get; } = new(new Guid("15D6493F-549D-455F-9FFF-A0D589FD7DA2"), "Equipped Weapon Count", string.Empty);
+
+ ///
+ /// Gets the magic speed attribute definition which is used for some skills.
+ ///
+ public static AttributeDefinition MagicSpeed { get; } = new(new Guid("AE32AA45-9C18-43B3-9F7B-648FD7F4B0AD"), "Magic Speed", string.Empty);
+
+ ///
+ /// Gets the walk speed attribute definition.
+ ///
+ public static AttributeDefinition WalkSpeed { get; } = new(new Guid("9CDDC598-E5F3-4372-9294-505455E4A40B"), "Walk Speed", string.Empty);
///
/// Gets the attack damage increase attribute definition.
diff --git a/src/GameLogic/ItemConstants.cs b/src/GameLogic/ItemConstants.cs
index e3201cb88..7fe87facb 100644
--- a/src/GameLogic/ItemConstants.cs
+++ b/src/GameLogic/ItemConstants.cs
@@ -7,7 +7,7 @@ namespace MUnique.OpenMU.GameLogic;
///
/// A central place to keep item identifiers, so we can keep track of them.
///
-internal class ItemConstants
+public class ItemConstants
{
///
/// Gets the identifier for the summon orb.
diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs
index 48402078f..2ef5335a5 100644
--- a/src/GameLogic/Player.cs
+++ b/src/GameLogic/Player.cs
@@ -4,6 +4,7 @@
namespace MUnique.OpenMU.GameLogic;
+using System;
using System.Threading;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
@@ -29,6 +30,7 @@ namespace MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
using Nito.AsyncEx;
+using static MUnique.OpenMU.GameLogic.Views.Character.IUpdateStatsPlugIn;
///
/// The base implementation of a player.
@@ -624,13 +626,13 @@ public bool IsAnySelfDefenseActive()
if (Rand.NextRandomBool(this.Attributes[Stats.FullyRecoverHealthAfterHitChance]))
{
this.Attributes[Stats.CurrentHealth] = this.Attributes[Stats.MaximumHealth];
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Health)).ConfigureAwait(false);
}
if (Rand.NextRandomBool(this.Attributes[Stats.FullyRecoverManaAfterHitChance]))
{
this.Attributes[Stats.CurrentMana] = this.Attributes[Stats.MaximumMana];
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
}
await this.HitAsync(hitInfo, attacker, skill?.Skill).ConfigureAwait(false);
@@ -733,8 +735,7 @@ public async ValueTask AfterKilledMonsterAsync()
this.Attributes[recoverAfterMonsterKill.CurrentAttribute] = (uint)Math.Min(this.Attributes[recoverAfterMonsterKill.MaximumAttribute], this.Attributes[recoverAfterMonsterKill.CurrentAttribute] + additionalValue);
}
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync()).ConfigureAwait(false);
}
///
@@ -1229,8 +1230,7 @@ public async Task RegenerateAsync()
attributes[r.MaximumAttribute]);
}
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync()).ConfigureAwait(false);
await this.RegenerateHeroStateAsync().ConfigureAwait(false);
}
@@ -1339,7 +1339,8 @@ public async ValueTask TryConsumeForSkillAsync(Skill skill)
this.Attributes![requirement.Attribute] -= this.GetRequiredValue(requirement);
}
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
+
return true;
}
@@ -2095,6 +2096,7 @@ private async ValueTask OnPlayerEnteredWorldAsync()
this.Attributes.GetOrCreateAttribute(Stats.MaximumHealth).ValueChanged += this.OnMaximumHealthOrShieldChanged;
this.Attributes.GetOrCreateAttribute(Stats.MaximumShield).ValueChanged += this.OnMaximumHealthOrShieldChanged;
this.Attributes.GetOrCreateAttribute(Stats.TransformationSkin).ValueChanged += this.OnTransformationSkinChanged;
+ this.Attributes.GetOrCreateAttribute(Stats.AttackSpeed).ValueChanged += this.OnAttackSpeedChanged;
var ammoAttribute = this.Attributes.GetOrCreateAttribute(Stats.AmmunitionAmount);
this.Attributes[Stats.AmmunitionAmount] = (float)(this.Inventory?.EquippedAmmunitionItem?.Durability ?? 0);
@@ -2189,8 +2191,9 @@ private async void OnMaximumHealthOrShieldChanged(object? sender, EventArgs args
{
this.Attributes![Stats.CurrentHealth] = Math.Min(this.Attributes[Stats.CurrentHealth], this.Attributes[Stats.MaximumHealth]);
this.Attributes[Stats.CurrentShield] = Math.Min(this.Attributes[Stats.CurrentShield], this.Attributes[Stats.MaximumShield]);
- await this.InvokeViewPlugInAsync(p => p.UpdateMaximumHealthAsync()).ConfigureAwait(false);
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
+
+ await this.InvokeViewPlugInAsync(p => p.UpdateMaximumStatsAsync(UpdatedStats.Health)).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Health)).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -2231,8 +2234,21 @@ private async void OnMaximumManaOrAbilityChanged(object? sender, EventArgs args)
{
this.Attributes![Stats.CurrentMana] = Math.Min(this.Attributes[Stats.CurrentMana], this.Attributes[Stats.MaximumMana]);
this.Attributes[Stats.CurrentAbility] = Math.Min(this.Attributes[Stats.CurrentAbility], this.Attributes[Stats.MaximumAbility]);
- await this.InvokeViewPlugInAsync(p => p.UpdateMaximumManaAsync()).ConfigureAwait(false);
- await this.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateMaximumStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ this.Logger.LogError(ex, nameof(this.OnMaximumManaOrAbilityChanged));
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Catching all Exceptions.")]
+ private async void OnAttackSpeedChanged(object? sender, EventArgs args)
+ {
+ try
+ {
+ await this.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(UpdatedStats.Speed)).ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/src/GameLogic/PlayerActions/Character/IncreaseStatsAction.cs b/src/GameLogic/PlayerActions/Character/IncreaseStatsAction.cs
index cedfcf2dd..ecd813452 100644
--- a/src/GameLogic/PlayerActions/Character/IncreaseStatsAction.cs
+++ b/src/GameLogic/PlayerActions/Character/IncreaseStatsAction.cs
@@ -41,6 +41,18 @@ public async ValueTask IncreaseStatsAsync(Player player, AttributeDefinition tar
var attributeDef = selectedCharacter.CharacterClass?.GetStatAttribute(targetAttribute);
if (attributeDef is { IncreasableByPlayer: true })
{
+ if (attributeDef.Attribute?.MaximumValue is { } maximumValue
+ && player.Attributes![attributeDef.Attribute] is { } current
+ && current + amount > maximumValue)
+ {
+ amount = (ushort)(maximumValue - current);
+ if (amount == 0)
+ {
+ await player.InvokeViewPlugInAsync(p => p.ShowMessageAsync($"Maximum of {attributeDef.Attribute?.MaximumValue} {attributeDef.Attribute?.Designation} has been reached.", MessageType.BlueNormal)).ConfigureAwait(false);
+ return;
+ }
+ }
+
player.Attributes![attributeDef.Attribute] += amount;
selectedCharacter.LevelUpPoints -= Math.Min(selectedCharacter.LevelUpPoints, amount);
diff --git a/src/GameLogic/PlayerActions/ItemConsumeActions/AlcoholConsumeHandlerPlugIn.cs b/src/GameLogic/PlayerActions/ItemConsumeActions/AlcoholConsumeHandlerPlugIn.cs
index 3bc03506d..d30d97575 100644
--- a/src/GameLogic/PlayerActions/ItemConsumeActions/AlcoholConsumeHandlerPlugIn.cs
+++ b/src/GameLogic/PlayerActions/ItemConsumeActions/AlcoholConsumeHandlerPlugIn.cs
@@ -15,7 +15,7 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;
///
[Guid("7FC2FE02-9215-4AD3-958F-D2279CD84266")]
[PlugIn(nameof(AlcoholConsumeHandlerPlugIn), "Plugin which handles the alcohol consumption.")]
-public class AlcoholConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
+public class AlcoholConsumeHandlerPlugIn : ApplyMagicEffectConsumeHandlerPlugIn
{
///
public override ItemIdentifier Key => ItemConstants.Alcohol;
@@ -25,7 +25,8 @@ public override async ValueTask ConsumeItemAsync(Player player, Item item,
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
- await player.InvokeViewPlugInAsync(p => p.DrinkAlcoholAsync()).ConfigureAwait(false);
+ var effectDefinition = item.Definition?.ConsumeEffect;
+ await player.InvokeViewPlugInAsync(p => p.ConsumeSpecialItemAsync(item, (ushort)(effectDefinition?.Duration?.ConstantValue.Value ?? 0))).ConfigureAwait(false);
return true;
}
diff --git a/src/GameLogic/PlayerActions/ItemConsumeActions/ApplyMagicEffectConsumeHandlerPlugIn.cs b/src/GameLogic/PlayerActions/ItemConsumeActions/ApplyMagicEffectConsumeHandlerPlugIn.cs
index 65fe91381..007c9cb66 100644
--- a/src/GameLogic/PlayerActions/ItemConsumeActions/ApplyMagicEffectConsumeHandlerPlugIn.cs
+++ b/src/GameLogic/PlayerActions/ItemConsumeActions/ApplyMagicEffectConsumeHandlerPlugIn.cs
@@ -18,13 +18,30 @@ public class ApplyMagicEffectConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
///
public override async ValueTask ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
- if (!await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
+ if (item.Definition?.ConsumeEffect is not { } effectDefinition)
{
return false;
}
- if (item.Definition?.ConsumeEffect is not { } effectDefinition
- || !effectDefinition.PowerUpDefinitions.Any()
+ return await this.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, effectDefinition).ConfigureAwait(false);
+ }
+
+ ///
+ /// Consumes the item at the specified slot with the specified effect and reduces its durability by one.
+ /// If the durability has reached 0, the item is getting destroyed.
+ /// If a target slot is specified, the consumption targets the item on this slot (e.g. upgrade of an item by a jewel).
+ ///
+ /// The player which is consuming.
+ /// The item which gets consumed.
+ /// The item which is the target of the consumption (e.g. upgrade target of a jewel).
+ /// In case the item is a fruit, this parameter defines how the fruit should be used.
+ /// The effect definition.
+ ///
+ /// The success of the consumption.
+ ///
+ protected async ValueTask ConsumeItemAsyncCore(Player player, Item item, Item? targetItem, FruitUsage fruitUsage, MagicEffectDefinition effectDefinition)
+ {
+ if (!effectDefinition.PowerUpDefinitions.Any()
|| effectDefinition.Duration?.ConstantValue.Value is not { } durationInSeconds)
{
return false;
@@ -44,6 +61,11 @@ public override async ValueTask ConsumeItemAsync(Player player, Item item,
return false;
}
+ if (!await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
+ {
+ return false;
+ }
+
var effect = new MagicEffect(TimeSpan.FromSeconds(durationInSeconds), effectDefinition, boosts!);
await player.MagicEffectList.AddEffectAsync(effect).ConfigureAwait(false);
return true;
diff --git a/src/GameLogic/PlayerActions/ItemConsumeActions/HealthPotionConsumeHandlerPlugIn.cs b/src/GameLogic/PlayerActions/ItemConsumeActions/HealthPotionConsumeHandlerPlugIn.cs
index aa91b1a62..dd230f5c9 100644
--- a/src/GameLogic/PlayerActions/ItemConsumeActions/HealthPotionConsumeHandlerPlugIn.cs
+++ b/src/GameLogic/PlayerActions/ItemConsumeActions/HealthPotionConsumeHandlerPlugIn.cs
@@ -25,6 +25,6 @@ public abstract class HealthPotionConsumeHandlerPlugIn : RecoverConsumeHandlerPl
protected override async ValueTask OnAfterRecoverAsync(Player player)
{
// maybe instead of calling UpdateCurrentHealth etc. provide a more general method where we pass this.CurrentAttribute. The view can then decide what to do with it.
- await player.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
+ await player.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(IUpdateStatsPlugIn.UpdatedStats.Health)).ConfigureAwait(false);
}
}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/ItemConsumeActions/ManaPotionConsumehandler.cs b/src/GameLogic/PlayerActions/ItemConsumeActions/ManaPotionConsumehandler.cs
index d27931806..30efe2401 100644
--- a/src/GameLogic/PlayerActions/ItemConsumeActions/ManaPotionConsumehandler.cs
+++ b/src/GameLogic/PlayerActions/ItemConsumeActions/ManaPotionConsumehandler.cs
@@ -24,6 +24,6 @@ public abstract class ManaPotionConsumeHandler : RecoverConsumeHandlerPlugIn.Man
///
protected override async ValueTask OnAfterRecoverAsync(Player player)
{
- await player.InvokeViewPlugInAsync(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
+ await player.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(IUpdateStatsPlugIn.UpdatedStats.Mana)).ConfigureAwait(false);
}
}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/ItemConsumeActions/SiegePotionConsumeHandlerPlugIn.cs b/src/GameLogic/PlayerActions/ItemConsumeActions/SiegePotionConsumeHandlerPlugIn.cs
new file mode 100644
index 000000000..1200991c7
--- /dev/null
+++ b/src/GameLogic/PlayerActions/ItemConsumeActions/SiegePotionConsumeHandlerPlugIn.cs
@@ -0,0 +1,49 @@
+// -----------------------------------------------------------------------
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+// -----------------------------------------------------------------------
+
+namespace MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
+using MUnique.OpenMU.GameLogic.Views;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// The alcohol consume handler.
+///
+[Guid("9D50CE95-5354-43A7-8DD5-9D6953700DFA")]
+[PlugIn(nameof(SiegePotionConsumeHandlerPlugIn), "Plugin which handles the siege potion consumption.")]
+public class SiegePotionConsumeHandlerPlugIn : ApplyMagicEffectConsumeHandlerPlugIn
+{
+ ///
+ public override ItemIdentifier Key => ItemConstants.SiegePotion;
+
+ ///
+ public override async ValueTask ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
+ {
+ if (item.Level == 0
+ && player.GameContext.Configuration.MagicEffects.FirstOrDefault(e => e.Number == 10) is { } blessEffectDefinition)
+ {
+ return await base.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, blessEffectDefinition).ConfigureAwait(false);
+ }
+
+ if (item.Level == 1
+ && player.GameContext.Configuration.MagicEffects.FirstOrDefault(e => e.Number == 11) is { } effectDefinition)
+ {
+ if (await base.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, effectDefinition).ConfigureAwait(false))
+ {
+ await player.InvokeViewPlugInAsync(p => p.ConsumeSpecialItemAsync(item, (ushort)(effectDefinition.Duration?.ConstantValue.Value ?? 0))).ConfigureAwait(false);
+ return true;
+ }
+ }
+ else
+ {
+ await player.ShowMessageAsync("Effect for item not found.").ConfigureAwait(false);
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs
index 05dc28838..5cf55d416 100644
--- a/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs
+++ b/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs
@@ -31,7 +31,7 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab
if (playerAttributes != null)
{
playerAttributes[Stats.CurrentHealth] = (uint)Math.Min(playerAttributes[Stats.MaximumHealth], playerAttributes[Stats.CurrentHealth] + hitInfo.Value.HealthDamage);
- await attackerPlayer.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
+ await attackerPlayer.InvokeViewPlugInAsync(p => p.UpdateCurrentStatsAsync(IUpdateStatsPlugIn.UpdatedStats.Health)).ConfigureAwait(false);
}
}
}
diff --git a/src/GameLogic/Views/Character/IUpdateCurrentHealthPlugIn.cs b/src/GameLogic/Views/Character/IUpdateCurrentHealthPlugIn.cs
deleted file mode 100644
index da9d4a4c6..000000000
--- a/src/GameLogic/Views/Character/IUpdateCurrentHealthPlugIn.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameLogic.Views.Character;
-
-///
-/// Interface of a view whose implementation informs about an updated current health.
-///
-public interface IUpdateCurrentHealthPlugIn : IViewPlugIn
-{
- ///
- /// Updates the current health.
- ///
- ValueTask UpdateCurrentHealthAsync();
-}
\ No newline at end of file
diff --git a/src/GameLogic/Views/Character/IUpdateCurrentManaPlugIn.cs b/src/GameLogic/Views/Character/IUpdateCurrentManaPlugIn.cs
deleted file mode 100644
index f259fa355..000000000
--- a/src/GameLogic/Views/Character/IUpdateCurrentManaPlugIn.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameLogic.Views.Character;
-
-///
-/// Interface of a view whose implementation informs about an updated current mana.
-///
-public interface IUpdateCurrentManaPlugIn : IViewPlugIn
-{
- ///
- /// Updates the current mana.
- ///
- ValueTask UpdateCurrentManaAsync();
-}
\ No newline at end of file
diff --git a/src/GameLogic/Views/Character/IUpdateMaximumHealthPlugIn.cs b/src/GameLogic/Views/Character/IUpdateMaximumHealthPlugIn.cs
deleted file mode 100644
index 967c3b766..000000000
--- a/src/GameLogic/Views/Character/IUpdateMaximumHealthPlugIn.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameLogic.Views.Character;
-
-///
-/// Interface of a view whose implementation informs about an updated maximum health.
-///
-public interface IUpdateMaximumHealthPlugIn : IViewPlugIn
-{
- ///
- /// Updates the maximum health.
- ///
- ValueTask UpdateMaximumHealthAsync();
-}
\ No newline at end of file
diff --git a/src/GameLogic/Views/Character/IUpdateMaximumManaPlugIn.cs b/src/GameLogic/Views/Character/IUpdateMaximumManaPlugIn.cs
deleted file mode 100644
index 34a096165..000000000
--- a/src/GameLogic/Views/Character/IUpdateMaximumManaPlugIn.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameLogic.Views.Character;
-
-///
-/// Interface of a view whose implementation informs about an updated maximum mana.
-///
-public interface IUpdateMaximumManaPlugIn : IViewPlugIn
-{
- ///
- /// Updates the maximum mana.
- ///
- ValueTask UpdateMaximumManaAsync();
-}
\ No newline at end of file
diff --git a/src/GameLogic/Views/Character/IUpdateStatsPlugIn.cs b/src/GameLogic/Views/Character/IUpdateStatsPlugIn.cs
new file mode 100644
index 000000000..6e6d81f42
--- /dev/null
+++ b/src/GameLogic/Views/Character/IUpdateStatsPlugIn.cs
@@ -0,0 +1,53 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.Views.Character;
+
+using MUnique.OpenMU.AttributeSystem;
+
+///
+/// Interface of a view whose implementation informs about updated stats.
+///
+public interface IUpdateStatsPlugIn : IViewPlugIn
+{
+ ///
+ /// Updates the maximum stats.
+ ///
+ /// The updated stats.
+ ValueTask UpdateMaximumStatsAsync(UpdatedStats updatedStats = UpdatedStats.Health | UpdatedStats.Mana | UpdatedStats.Speed);
+
+ ///
+ /// Updates the current stats.
+ ///
+ /// The updated stats.
+ ValueTask UpdateCurrentStatsAsync(UpdatedStats updatedStats = UpdatedStats.Health | UpdatedStats.Mana | UpdatedStats.Speed);
+
+ ///
+ /// The updated stat.
+ /// This might be replaced by the actual in the future.
+ ///
+ [Flags]
+ public enum UpdatedStats
+ {
+ ///
+ /// Undefined.
+ ///
+ Undefined,
+
+ ///
+ /// The health or shield changed.
+ ///
+ Health = 0x01,
+
+ ///
+ /// The mana or ability changed.
+ ///
+ Mana = 0x02,
+
+ ///
+ /// The attack speed changed.
+ ///
+ Speed = 0x04,
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/Views/IDrinkAlcoholPlugIn.cs b/src/GameLogic/Views/IConsumeSpecialItemPlugIn.cs
similarity index 58%
rename from src/GameLogic/Views/IDrinkAlcoholPlugIn.cs
rename to src/GameLogic/Views/IConsumeSpecialItemPlugIn.cs
index 49088cdcd..e211bdffb 100644
--- a/src/GameLogic/Views/IDrinkAlcoholPlugIn.cs
+++ b/src/GameLogic/Views/IConsumeSpecialItemPlugIn.cs
@@ -7,10 +7,13 @@ namespace MUnique.OpenMU.GameLogic.Views;
///
/// Interface of a view whose implementation informs about the alcohol consumption of the own character. The character appears to be red (drunken).
///
-public interface IDrinkAlcoholPlugIn : IViewPlugIn
+public interface IConsumeSpecialItemPlugIn : IViewPlugIn
{
///
- /// Shows the effects of drinking alcohol.
+ /// Consumes the special item.
///
- ValueTask DrinkAlcoholAsync();
+ /// The item.
+ /// The effect time in seconds.
+ ///
+ ValueTask ConsumeSpecialItemAsync(Item item, ushort effectTimeInSeconds);
}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateCharacterStatsExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateCharacterStatsExtendedPlugIn.cs
index 0cd9a8e28..80124d500 100644
--- a/src/GameServer/RemoteView/Character/UpdateCharacterStatsExtendedPlugIn.cs
+++ b/src/GameServer/RemoteView/Character/UpdateCharacterStatsExtendedPlugIn.cs
@@ -37,6 +37,7 @@ public async ValueTask UpdateCharacterStatsAsync()
return;
}
+ var maxAttackSpeed = this._player.GameContext.Configuration.Attributes.FirstOrDefault(a => a == Stats.AttackSpeed)?.MaximumValue ?? 200;
await connection.SendCharacterInformationExtendedAsync(
this._player.Position.X,
this._player.Position.Y,
@@ -65,8 +66,8 @@ await connection.SendCharacterInformationExtendedAsync(
(ushort)this._player.SelectedCharacter.UsedNegFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(ushort)this._player.Attributes[Stats.AttackSpeed],
- (ushort)this._player.Attributes[Stats.AttackSpeed], // todo: implement MagicSpeed
- 200, // todo: This is the maximum attack speed, make configurable.
+ (ushort)this._player.Attributes[Stats.MagicSpeed],
+ (ushort)maxAttackSpeed,
(byte)this._player.SelectedCharacter.InventoryExtensions)
.ConfigureAwait(false);
diff --git a/src/GameServer/RemoteView/Character/UpdateCurrentHealthExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateCurrentHealthExtendedPlugIn.cs
deleted file mode 100644
index daf062c2c..000000000
--- a/src/GameServer/RemoteView/Character/UpdateCurrentHealthExtendedPlugIn.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.Network.PlugIns;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The extended implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn(nameof(UpdateCurrentHealthExtendedPlugIn), "The extended implementation of the IUpdateCurrentHealthPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("C609BC7E-170B-4C79-94E5-D97AB9A3CB4B")]
-[MinimumClient(106, 3, ClientLanguage.Invariant)]
-public class UpdateCurrentHealthExtendedPlugIn : IUpdateCurrentHealthPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateCurrentHealthExtendedPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateCurrentHealthAsync()
- {
- if (this._player.Attributes is null)
- {
- return;
- }
-
- await this._player.Connection.SendCurrentHealthAndShieldExtendedAsync(
- (uint)Math.Max(this._player.Attributes[Stats.CurrentHealth], 0f),
- (uint)Math.Max(this._player.Attributes[Stats.CurrentShield], 0f)).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateCurrentHealthPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateCurrentHealthPlugIn.cs
deleted file mode 100644
index 71847fab1..000000000
--- a/src/GameServer/RemoteView/Character/UpdateCurrentHealthPlugIn.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The default implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn("UpdateCurrentHealthPlugIn", "The default implementation of the IUpdateCurrentHealthPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("0c832ed3-fea7-4239-8208-b46897b44c84")]
-public class UpdateCurrentHealthPlugIn : IUpdateCurrentHealthPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateCurrentHealthPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateCurrentHealthAsync()
- {
- if (this._player.Attributes is null)
- {
- return;
- }
-
- await this._player.Connection.SendCurrentHealthAndShieldAsync(
- (ushort)Math.Max(this._player.Attributes[Stats.CurrentHealth], 0f),
- (ushort)Math.Max(this._player.Attributes[Stats.CurrentShield], 0f)).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateCurrentManaExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateCurrentManaExtendedPlugIn.cs
deleted file mode 100644
index eb58815d1..000000000
--- a/src/GameServer/RemoteView/Character/UpdateCurrentManaExtendedPlugIn.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.Network.PlugIns;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The extended implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn(nameof(UpdateCurrentManaExtendedPlugIn), "The extended implementation of the IUpdateCurrentManaPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("1F99BFB4-35FC-489B-AB3C-E0738314DF37")]
-[MinimumClient(106, 3, ClientLanguage.Invariant)]
-public class UpdateCurrentManaExtendedPlugIn : IUpdateCurrentManaPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateCurrentManaExtendedPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateCurrentManaAsync()
- {
- if (this._player.Attributes is null)
- {
- return;
- }
-
- await this._player.Connection.SendCurrentManaAndAbilityExtendedAsync(
- (uint)this._player.Attributes[Stats.CurrentMana],
- (uint)this._player.Attributes[Stats.CurrentAbility]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateCurrentManaPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateCurrentManaPlugIn.cs
deleted file mode 100644
index b661c3625..000000000
--- a/src/GameServer/RemoteView/Character/UpdateCurrentManaPlugIn.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The default implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn("UpdateCurrentManaPlugIn", "The default implementation of the IUpdateCurrentManaPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("814fcc24-022a-47c8-b7d2-b1d1ca0208cb")]
-public class UpdateCurrentManaPlugIn : IUpdateCurrentManaPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateCurrentManaPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateCurrentManaAsync()
- {
- if (this._player.Attributes is null)
- {
- return;
- }
-
- await this._player.Connection.SendCurrentManaAndAbilityAsync(
- (ushort)this._player.Attributes[Stats.CurrentMana],
- (ushort)this._player.Attributes[Stats.CurrentAbility]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateMaximumHealthExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateMaximumHealthExtendedPlugIn.cs
deleted file mode 100644
index 5dd404843..000000000
--- a/src/GameServer/RemoteView/Character/UpdateMaximumHealthExtendedPlugIn.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.Network.PlugIns;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The extended implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn(nameof(UpdateMaximumHealthExtendedPlugIn), "The extended implementation of the IUpdateMaximumHealthPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("7A287D6D-5C32-4FA9-9A0D-A4E0DEB053D1")]
-[MinimumClient(106, 3, ClientLanguage.Invariant)]
-public class UpdateMaximumHealthExtendedPlugIn : IUpdateMaximumHealthPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateMaximumHealthExtendedPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateMaximumHealthAsync()
- {
- if (this._player.Attributes is null
- || !(this._player.Connection?.Connected ?? false))
- {
- return;
- }
-
- await this._player.Connection.SendMaximumHealthAndShieldExtendedAsync(
- (uint)this._player.Attributes[Stats.MaximumHealth],
- (uint)this._player.Attributes[Stats.MaximumShield]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateMaximumHealthPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateMaximumHealthPlugIn.cs
deleted file mode 100644
index e683b6f7b..000000000
--- a/src/GameServer/RemoteView/Character/UpdateMaximumHealthPlugIn.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The default implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn("UpdateMaximumHealthPlugIn", "The default implementation of the IUpdateMaximumHealthPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("6f8e7d9a-7d15-4e76-a650-8bfa70c7298e")]
-public class UpdateMaximumHealthPlugIn : IUpdateMaximumHealthPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateMaximumHealthPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateMaximumHealthAsync()
- {
- if (this._player.Attributes is null
- || !(this._player.Connection?.Connected ?? false))
- {
- return;
- }
-
- await this._player.Connection.SendMaximumHealthAndShieldAsync(
- (ushort)this._player.Attributes[Stats.MaximumHealth],
- (ushort)this._player.Attributes[Stats.MaximumShield]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateMaximumManaExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateMaximumManaExtendedPlugIn.cs
deleted file mode 100644
index 899f989c9..000000000
--- a/src/GameServer/RemoteView/Character/UpdateMaximumManaExtendedPlugIn.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.Network.PlugIns;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The extended implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn(nameof(UpdateMaximumManaExtendedPlugIn), "The extended implementation of the IUpdateMaximumManaPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("25AF11D5-FA10-4634-AF5A-CBF6F5E8BDFE")]
-[MinimumClient(106, 3, ClientLanguage.Invariant)]
-public class UpdateMaximumManaExtendedPlugIn : IUpdateMaximumManaPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateMaximumManaExtendedPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateMaximumManaAsync()
- {
- if (this._player.Attributes is null
- || !(this._player.Connection?.Connected ?? false))
- {
- return;
- }
-
- await this._player.Connection.SendMaximumManaAndAbilityExtendedAsync(
- (uint)this._player.Attributes[Stats.MaximumMana],
- (uint)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateMaximumManaPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateMaximumManaPlugIn.cs
deleted file mode 100644
index eff798d68..000000000
--- a/src/GameServer/RemoteView/Character/UpdateMaximumManaPlugIn.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView.Character;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Attributes;
-using MUnique.OpenMU.GameLogic.Views.Character;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The default implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn("UpdateMaximumManaPlugIn", "The default implementation of the IUpdateMaximumManaPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("dc84be82-7ab0-4348-aa34-4a3dc8c1ee7a")]
-public class UpdateMaximumManaPlugIn : IUpdateMaximumManaPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public UpdateMaximumManaPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask UpdateMaximumManaAsync()
- {
- if (this._player.Attributes is null
- || !(this._player.Connection?.Connected ?? false))
- {
- return;
- }
-
- await this._player.Connection.SendMaximumManaAndAbilityAsync(
- (ushort)this._player.Attributes[Stats.MaximumMana],
- (ushort)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateStatsExtendedPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateStatsExtendedPlugIn.cs
new file mode 100644
index 000000000..1e70249c4
--- /dev/null
+++ b/src/GameServer/RemoteView/Character/UpdateStatsExtendedPlugIn.cs
@@ -0,0 +1,63 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameServer.RemoteView.Character;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.GameLogic.Attributes;
+using MUnique.OpenMU.GameLogic.Views.Character;
+using MUnique.OpenMU.Network.Packets.ServerToClient;
+using MUnique.OpenMU.Network.PlugIns;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// The extended implementation of the which is forwarding everything to the game client with specific data packets.
+///
+[PlugIn(nameof(UpdateStatsExtendedPlugIn), "The extended implementation of the IUpdateStatsPlugIn which is forwarding everything to the game client with specific data packets.")]
+[Guid("E9A1CCBE-416F-41BA-8E74-74CBEB7042DD")]
+[MinimumClient(106, 3, ClientLanguage.Invariant)]
+public class UpdateStatsExtendedPlugIn : IUpdateStatsPlugIn
+{
+ private readonly RemotePlayer _player;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The player.
+ public UpdateStatsExtendedPlugIn(RemotePlayer player) => this._player = player;
+
+ ///
+ public async ValueTask UpdateMaximumStatsAsync(IUpdateStatsPlugIn.UpdatedStats updatedStats = IUpdateStatsPlugIn.UpdatedStats.Undefined)
+ {
+ if (this._player.Attributes is null
+ || !(this._player.Connection?.Connected ?? false))
+ {
+ return;
+ }
+
+ await this._player.Connection.SendMaximumStatsExtendedAsync(
+ (uint)this._player.Attributes[Stats.MaximumHealth],
+ (uint)this._player.Attributes[Stats.MaximumShield],
+ (uint)this._player.Attributes[Stats.MaximumMana],
+ (uint)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
+ }
+
+ ///
+ public async ValueTask UpdateCurrentStatsAsync(IUpdateStatsPlugIn.UpdatedStats updatedStats = IUpdateStatsPlugIn.UpdatedStats.Undefined)
+ {
+ if (this._player.Attributes is null
+ || !(this._player.Connection?.Connected ?? false))
+ {
+ return;
+ }
+
+ await this._player.Connection.SendCurrentStatsExtendedAsync(
+ (uint)this._player.Attributes[Stats.CurrentHealth],
+ (uint)this._player.Attributes[Stats.CurrentShield],
+ (uint)this._player.Attributes[Stats.CurrentMana],
+ (uint)this._player.Attributes[Stats.CurrentAbility],
+ (ushort)this._player.Attributes[Stats.AttackSpeed],
+ (ushort)this._player.Attributes[Stats.MagicSpeed]).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/Character/UpdateStatsPlugIn.cs b/src/GameServer/RemoteView/Character/UpdateStatsPlugIn.cs
new file mode 100644
index 000000000..bbffdd33a
--- /dev/null
+++ b/src/GameServer/RemoteView/Character/UpdateStatsPlugIn.cs
@@ -0,0 +1,75 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameServer.RemoteView.Character;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.GameLogic.Attributes;
+using MUnique.OpenMU.GameLogic.Views.Character;
+using MUnique.OpenMU.Network.Packets.ServerToClient;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// The default implementation of the which is forwarding everything to the game client with specific data packets.
+///
+[PlugIn(nameof(UpdateStatsPlugIn), "The default implementation of the IUpdateStatsPlugIn which is forwarding everything to the game client with specific data packets.")]
+[Guid("2A8BFB0C-2AFF-4A52-B390-5A68D5C5F26A")]
+public class UpdateStatsPlugIn : IUpdateStatsPlugIn
+{
+ private readonly RemotePlayer _player;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The player.
+ public UpdateStatsPlugIn(RemotePlayer player) => this._player = player;
+
+ ///
+ public async ValueTask UpdateMaximumStatsAsync(IUpdateStatsPlugIn.UpdatedStats updatedStats = IUpdateStatsPlugIn.UpdatedStats.Undefined | IUpdateStatsPlugIn.UpdatedStats.Health | IUpdateStatsPlugIn.UpdatedStats.Mana | IUpdateStatsPlugIn.UpdatedStats.Speed)
+ {
+ if (this._player.Attributes is null
+ || !(this._player.Connection?.Connected ?? false))
+ {
+ return;
+ }
+
+ if (updatedStats.HasFlag(IUpdateStatsPlugIn.UpdatedStats.Health))
+ {
+ await this._player.Connection.SendMaximumHealthAndShieldAsync(
+ (ushort)Math.Max(this._player.Attributes[Stats.MaximumHealth], 0f),
+ (ushort)Math.Max(this._player.Attributes[Stats.MaximumShield], 0f)).ConfigureAwait(false);
+ }
+
+ if (updatedStats.HasFlag(IUpdateStatsPlugIn.UpdatedStats.Mana))
+ {
+ await this._player.Connection.SendMaximumManaAndAbilityAsync(
+ (ushort)Math.Max(this._player.Attributes[Stats.MaximumMana], 0f),
+ (ushort)Math.Max(this._player.Attributes[Stats.MaximumAbility], 0f)).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public async ValueTask UpdateCurrentStatsAsync(IUpdateStatsPlugIn.UpdatedStats updatedStats = IUpdateStatsPlugIn.UpdatedStats.Undefined | IUpdateStatsPlugIn.UpdatedStats.Health | IUpdateStatsPlugIn.UpdatedStats.Mana | IUpdateStatsPlugIn.UpdatedStats.Speed)
+ {
+ if (this._player.Attributes is null
+ || !(this._player.Connection?.Connected ?? false))
+ {
+ return;
+ }
+
+ if (updatedStats.HasFlag(IUpdateStatsPlugIn.UpdatedStats.Health))
+ {
+ await this._player.Connection.SendCurrentHealthAndShieldAsync(
+ (ushort)Math.Max(this._player.Attributes[Stats.CurrentHealth], 0f),
+ (ushort)Math.Max(this._player.Attributes[Stats.CurrentShield], 0f)).ConfigureAwait(false);
+ }
+
+ if (updatedStats.HasFlag(IUpdateStatsPlugIn.UpdatedStats.Mana))
+ {
+ await this._player.Connection.SendCurrentHealthAndShieldAsync(
+ (ushort)Math.Max(this._player.Attributes[Stats.CurrentMana], 0f),
+ (ushort)Math.Max(this._player.Attributes[Stats.CurrentAbility], 0f)).ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/ConsumeSpecialItemPlugIn.cs b/src/GameServer/RemoteView/ConsumeSpecialItemPlugIn.cs
new file mode 100644
index 000000000..2cacedbc7
--- /dev/null
+++ b/src/GameServer/RemoteView/ConsumeSpecialItemPlugIn.cs
@@ -0,0 +1,51 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameServer.RemoteView;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Entities;
+using MUnique.OpenMU.GameLogic;
+using MUnique.OpenMU.GameLogic.Views;
+using MUnique.OpenMU.Network.Packets.ServerToClient;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// The default implementation of the which is forwarding everything to the game client with specific data packets.
+///
+[PlugIn(nameof(ConsumeSpecialItemPlugIn), "The default implementation of the IConsumeSpecialItemPlugIn which is forwarding everything to the game client with specific data packets.")]
+[Guid("a31546d8-bf79-43dd-872c-52f24ea9bca9")]
+public class ConsumeSpecialItemPlugIn : IConsumeSpecialItemPlugIn
+{
+ private readonly RemotePlayer _player;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The player.
+ public ConsumeSpecialItemPlugIn(RemotePlayer player) => this._player = player;
+
+ ///
+ public async ValueTask ConsumeSpecialItemAsync(Item item, ushort effectTimeInSeconds)
+ {
+ if (item.Definition is null)
+ {
+ return;
+ }
+
+ var itemIdentifier = new ItemIdentifier(item.Definition.Number, item.Definition.Group);
+ if (itemIdentifier == ItemConstants.Alcohol)
+ {
+ await this._player.Connection.SendConsumeItemWithEffectAsync(ConsumeItemWithEffect.ConsumedItemType.Ale, effectTimeInSeconds).ConfigureAwait(false);
+ }
+ else if (itemIdentifier == ItemConstants.SiegePotion && item.Level == 1)
+ {
+ await this._player.Connection.SendConsumeItemWithEffectAsync(ConsumeItemWithEffect.ConsumedItemType.PotionOfSoul, effectTimeInSeconds).ConfigureAwait(false);
+ }
+ else
+ {
+ // do nothing
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameServer/RemoteView/DrinkAlcoholPlugIn.cs b/src/GameServer/RemoteView/DrinkAlcoholPlugIn.cs
deleted file mode 100644
index b661b2abd..000000000
--- a/src/GameServer/RemoteView/DrinkAlcoholPlugIn.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Licensed under the MIT License. See LICENSE file in the project root for full license information.
-//
-
-namespace MUnique.OpenMU.GameServer.RemoteView;
-
-using System.Runtime.InteropServices;
-using MUnique.OpenMU.GameLogic.Views;
-using MUnique.OpenMU.Network.Packets.ServerToClient;
-using MUnique.OpenMU.PlugIns;
-
-///
-/// The default implementation of the which is forwarding everything to the game client with specific data packets.
-///
-[PlugIn("DrinkAlcoholPlugIn", "The default implementation of the IDrinkAlcoholPlugIn which is forwarding everything to the game client with specific data packets.")]
-[Guid("a31546d8-bf79-43dd-872c-52f24ea9bca9")]
-public class DrinkAlcoholPlugIn : IDrinkAlcoholPlugIn
-{
- private readonly RemotePlayer _player;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The player.
- public DrinkAlcoholPlugIn(RemotePlayer player) => this._player = player;
-
- ///
- public async ValueTask DrinkAlcoholAsync()
- {
- await this._player.Connection.SendConsumeItemWithEffectAsync(ConsumeItemWithEffect.ConsumedItemType.Ale, 80).ConfigureAwait(false);
- }
-}
\ No newline at end of file
diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
index ec13a7a62..d7d1e18d2 100644
--- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
+++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
@@ -1726,16 +1726,16 @@ int WritePacket()
}
///
- /// Sends a to this connection.
+ /// Sends a to this connection.
///
/// The connection.
/// The health.
/// The shield.
///
- /// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits.
+ /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
///
- public static async ValueTask SendCurrentHealthAndShieldExtendedAsync(this IConnection? connection, uint @health, uint @shield)
+ public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield)
{
if (connection is null)
{
@@ -1744,8 +1744,8 @@ public static async ValueTask SendCurrentHealthAndShieldExtendedAsync(this IConn
int WritePacket()
{
- var length = CurrentHealthAndShieldExtendedRef.Length;
- var packet = new CurrentHealthAndShieldExtendedRef(connection.Output.GetSpan(length)[..length]);
+ var length = MaximumHealthAndShieldRef.Length;
+ var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]);
packet.Health = @health;
packet.Shield = @shield;
@@ -1756,16 +1756,20 @@ int WritePacket()
}
///
- /// Sends a to this connection.
+ /// Sends a to this connection.
///
/// The connection.
/// The health.
/// The shield.
+ /// The mana.
+ /// The ability.
+ /// The attack speed.
+ /// The magic speed.
///
- /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
- /// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+ /// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits.
+ /// Causes reaction on client side: The values are updated on the game client user interface.
///
- public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection? connection, ushort @health, ushort @shield)
+ public static async ValueTask SendCurrentStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability, ushort @attackSpeed, ushort @magicSpeed)
{
if (connection is null)
{
@@ -1774,10 +1778,14 @@ public static async ValueTask SendMaximumHealthAndShieldAsync(this IConnection?
int WritePacket()
{
- var length = MaximumHealthAndShieldRef.Length;
- var packet = new MaximumHealthAndShieldRef(connection.Output.GetSpan(length)[..length]);
+ var length = CurrentStatsExtendedRef.Length;
+ var packet = new CurrentStatsExtendedRef(connection.Output.GetSpan(length)[..length]);
packet.Health = @health;
packet.Shield = @shield;
+ packet.Mana = @mana;
+ packet.Ability = @ability;
+ packet.AttackSpeed = @attackSpeed;
+ packet.MagicSpeed = @magicSpeed;
return packet.Header.Length;
}
@@ -1786,16 +1794,18 @@ int WritePacket()
}
///
- /// Sends a to this connection.
+ /// Sends a to this connection.
///
/// The connection.
/// The health.
/// The shield.
+ /// The mana.
+ /// The ability.
///
- /// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
- /// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+ /// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items.
+ /// Causes reaction on client side: The values are updated on the game client user interface.
///
- public static async ValueTask SendMaximumHealthAndShieldExtendedAsync(this IConnection? connection, uint @health, uint @shield)
+ public static async ValueTask SendMaximumStatsExtendedAsync(this IConnection? connection, uint @health, uint @shield, uint @mana, uint @ability)
{
if (connection is null)
{
@@ -1804,10 +1814,12 @@ public static async ValueTask SendMaximumHealthAndShieldExtendedAsync(this IConn
int WritePacket()
{
- var length = MaximumHealthAndShieldExtendedRef.Length;
- var packet = new MaximumHealthAndShieldExtendedRef(connection.Output.GetSpan(length)[..length]);
+ var length = MaximumStatsExtendedRef.Length;
+ var packet = new MaximumStatsExtendedRef(connection.Output.GetSpan(length)[..length]);
packet.Health = @health;
packet.Shield = @shield;
+ packet.Mana = @mana;
+ packet.Ability = @ability;
return packet.Header.Length;
}
@@ -1905,36 +1917,6 @@ int WritePacket()
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}
- ///
- /// Sends a to this connection.
- ///
- /// The connection.
- /// The mana.
- /// The ability.
- ///
- /// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill.
- /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
- ///
- public static async ValueTask SendCurrentManaAndAbilityExtendedAsync(this IConnection? connection, uint @mana, uint @ability)
- {
- if (connection is null)
- {
- return;
- }
-
- int WritePacket()
- {
- var length = CurrentManaAndAbilityExtendedRef.Length;
- var packet = new CurrentManaAndAbilityExtendedRef(connection.Output.GetSpan(length)[..length]);
- packet.Mana = @mana;
- packet.Ability = @ability;
-
- return packet.Header.Length;
- }
-
- await connection.SendAsync(WritePacket).ConfigureAwait(false);
- }
-
///
/// Sends a to this connection.
///
@@ -1965,36 +1947,6 @@ int WritePacket()
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}
- ///
- /// Sends a to this connection.
- ///
- /// The connection.
- /// The mana.
- /// The ability.
- ///
- /// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points.
- /// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
- ///
- public static async ValueTask SendMaximumManaAndAbilityExtendedAsync(this IConnection? connection, uint @mana, uint @ability)
- {
- if (connection is null)
- {
- return;
- }
-
- int WritePacket()
- {
- var length = MaximumManaAndAbilityExtendedRef.Length;
- var packet = new MaximumManaAndAbilityExtendedRef(connection.Output.GetSpan(length)[..length]);
- packet.Mana = @mana;
- packet.Ability = @ability;
-
- return packet.Header.Length;
- }
-
- await connection.SendAsync(WritePacket).ConfigureAwait(false);
- }
-
///
/// Sends a to this connection.
///
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
index 2cd7d3da8..b3410ae5c 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
@@ -8874,28 +8874,28 @@ public ushort Shield
///
-/// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits.
+/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
///
-public readonly struct CurrentHealthAndShieldExtended
+public readonly struct MaximumHealthAndShield
{
private readonly Memory _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public CurrentHealthAndShieldExtended(Memory data)
+ public MaximumHealthAndShield(Memory data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private CurrentHealthAndShieldExtended(Memory data, bool initialize)
+ private MaximumHealthAndShield(Memory data, bool initialize)
{
this._data = data;
if (initialize)
@@ -8922,12 +8922,12 @@ private CurrentHealthAndShieldExtended(Memory data, bool initialize)
/// Gets the operation sub-code of this data packet.
/// The is used as a grouping key.
///
- public static byte SubCode => 0xFF;
+ public static byte SubCode => 0xFE;
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 12;
+ public static int Length => 9;
///
/// Gets the header of this packet.
@@ -8937,60 +8937,60 @@ private CurrentHealthAndShieldExtended(Memory data, bool initialize)
///
/// Gets or sets the health.
///
- public uint Health
+ public ushort Health
{
- get => ReadUInt32LittleEndian(this._data.Span[4..]);
- set => WriteUInt32LittleEndian(this._data.Span[4..], value);
+ get => ReadUInt16BigEndian(this._data.Span[4..]);
+ set => WriteUInt16BigEndian(this._data.Span[4..], value);
}
///
/// Gets or sets the shield.
///
- public uint Shield
+ public ushort Shield
{
- get => ReadUInt32LittleEndian(this._data.Span[8..]);
- set => WriteUInt32LittleEndian(this._data.Span[8..], value);
+ get => ReadUInt16BigEndian(this._data.Span[7..]);
+ set => WriteUInt16BigEndian(this._data.Span[7..], value);
}
///
- /// Performs an implicit conversion from a Memory of bytes to a .
+ /// Performs an implicit conversion from a Memory of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator CurrentHealthAndShieldExtended(Memory packet) => new (packet, false);
+ public static implicit operator MaximumHealthAndShield(Memory packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Memory of bytes.
+ /// Performs an implicit conversion from to a Memory of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Memory(CurrentHealthAndShieldExtended packet) => packet._data;
+ public static implicit operator Memory(MaximumHealthAndShield packet) => packet._data;
}
///
-/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
-/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+/// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits.
+/// Causes reaction on client side: The values are updated on the game client user interface.
///
-public readonly struct MaximumHealthAndShield
+public readonly struct CurrentStatsExtended
{
private readonly Memory _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public MaximumHealthAndShield(Memory data)
+ public CurrentStatsExtended(Memory data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumHealthAndShield(Memory data, bool initialize)
+ private CurrentStatsExtended(Memory data, bool initialize)
{
this._data = data;
if (initialize)
@@ -9017,12 +9017,12 @@ private MaximumHealthAndShield(Memory data, bool initialize)
/// Gets the operation sub-code of this data packet.
/// The is used as a grouping key.
///
- public static byte SubCode => 0xFE;
+ public static byte SubCode => 0xFF;
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 9;
+ public static int Length => 24;
///
/// Gets the header of this packet.
@@ -9032,60 +9032,96 @@ private MaximumHealthAndShield(Memory data, bool initialize)
///
/// Gets or sets the health.
///
- public ushort Health
+ public uint Health
{
- get => ReadUInt16BigEndian(this._data.Span[4..]);
- set => WriteUInt16BigEndian(this._data.Span[4..], value);
+ get => ReadUInt32LittleEndian(this._data.Span[4..]);
+ set => WriteUInt32LittleEndian(this._data.Span[4..], value);
}
///
/// Gets or sets the shield.
///
- public ushort Shield
+ public uint Shield
{
- get => ReadUInt16BigEndian(this._data.Span[7..]);
- set => WriteUInt16BigEndian(this._data.Span[7..], value);
+ get => ReadUInt32LittleEndian(this._data.Span[8..]);
+ set => WriteUInt32LittleEndian(this._data.Span[8..], value);
}
///
- /// Performs an implicit conversion from a Memory of bytes to a .
+ /// Gets or sets the mana.
+ ///
+ public uint Mana
+ {
+ get => ReadUInt32LittleEndian(this._data.Span[12..]);
+ set => WriteUInt32LittleEndian(this._data.Span[12..], value);
+ }
+
+ ///
+ /// Gets or sets the ability.
+ ///
+ public uint Ability
+ {
+ get => ReadUInt32LittleEndian(this._data.Span[16..]);
+ set => WriteUInt32LittleEndian(this._data.Span[16..], value);
+ }
+
+ ///
+ /// Gets or sets the attack speed.
+ ///
+ public ushort AttackSpeed
+ {
+ get => ReadUInt16LittleEndian(this._data.Span[20..]);
+ set => WriteUInt16LittleEndian(this._data.Span[20..], value);
+ }
+
+ ///
+ /// Gets or sets the magic speed.
+ ///
+ public ushort MagicSpeed
+ {
+ get => ReadUInt16LittleEndian(this._data.Span[22..]);
+ set => WriteUInt16LittleEndian(this._data.Span[22..], value);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Memory of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator MaximumHealthAndShield(Memory packet) => new (packet, false);
+ public static implicit operator CurrentStatsExtended(Memory packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Memory of bytes.
+ /// Performs an implicit conversion from to a Memory of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Memory(MaximumHealthAndShield packet) => packet._data;
+ public static implicit operator Memory(CurrentStatsExtended packet) => packet._data;
}
///
-/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
-/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+/// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items.
+/// Causes reaction on client side: The values are updated on the game client user interface.
///
-public readonly struct MaximumHealthAndShieldExtended
+public readonly struct MaximumStatsExtended
{
private readonly Memory _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public MaximumHealthAndShieldExtended(Memory data)
+ public MaximumStatsExtended(Memory data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumHealthAndShieldExtended(Memory data, bool initialize)
+ private MaximumStatsExtended(Memory data, bool initialize)
{
this._data = data;
if (initialize)
@@ -9117,7 +9153,7 @@ private MaximumHealthAndShieldExtended(Memory data, bool initialize)
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 12;
+ public static int Length => 20;
///
/// Gets the header of this packet.
@@ -9143,18 +9179,36 @@ public uint Shield
}
///
- /// Performs an implicit conversion from a Memory of bytes to a .
+ /// Gets or sets the mana.
+ ///
+ public uint Mana
+ {
+ get => ReadUInt32LittleEndian(this._data.Span[12..]);
+ set => WriteUInt32LittleEndian(this._data.Span[12..], value);
+ }
+
+ ///
+ /// Gets or sets the ability.
+ ///
+ public uint Ability
+ {
+ get => ReadUInt32LittleEndian(this._data.Span[16..]);
+ set => WriteUInt32LittleEndian(this._data.Span[16..], value);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Memory of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator MaximumHealthAndShieldExtended(Memory packet) => new (packet, false);
+ public static implicit operator MaximumStatsExtended(Memory packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Memory of bytes.
+ /// Performs an implicit conversion from to a Memory of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Memory(MaximumHealthAndShieldExtended packet) => packet._data;
+ public static implicit operator Memory(MaximumStatsExtended packet) => packet._data;
}
@@ -9443,101 +9497,6 @@ public ushort Ability
}
-///
-/// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill.
-/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
-///
-public readonly struct CurrentManaAndAbilityExtended
-{
- private readonly Memory _data;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- public CurrentManaAndAbilityExtended(Memory data)
- : this(data, true)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- /// If set to true, the header data is automatically initialized and written to the underlying span.
- private CurrentManaAndAbilityExtended(Memory data, bool initialize)
- {
- this._data = data;
- if (initialize)
- {
- var header = this.Header;
- header.Type = HeaderType;
- header.Code = Code;
- header.Length = (byte)Math.Min(data.Length, Length);
- header.SubCode = SubCode;
- }
- }
-
- ///
- /// Gets the header type of this data packet.
- ///
- public static byte HeaderType => 0xC1;
-
- ///
- /// Gets the operation code of this data packet.
- ///
- public static byte Code => 0x27;
-
- ///
- /// Gets the operation sub-code of this data packet.
- /// The is used as a grouping key.
- ///
- public static byte SubCode => 0xFF;
-
- ///
- /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
- ///
- public static int Length => 12;
-
- ///
- /// Gets the header of this packet.
- ///
- public C1HeaderWithSubCode Header => new (this._data);
-
- ///
- /// Gets or sets the mana.
- ///
- public uint Mana
- {
- get => ReadUInt32LittleEndian(this._data.Span[4..]);
- set => WriteUInt32LittleEndian(this._data.Span[4..], value);
- }
-
- ///
- /// Gets or sets the ability.
- ///
- public uint Ability
- {
- get => ReadUInt32LittleEndian(this._data.Span[8..]);
- set => WriteUInt32LittleEndian(this._data.Span[8..], value);
- }
-
- ///
- /// Performs an implicit conversion from a Memory of bytes to a .
- ///
- /// The packet as span.
- /// The packet as struct.
- public static implicit operator CurrentManaAndAbilityExtended(Memory packet) => new (packet, false);
-
- ///
- /// Performs an implicit conversion from to a Memory of bytes.
- ///
- /// The packet as struct.
- /// The packet as byte span.
- public static implicit operator Memory(CurrentManaAndAbilityExtended packet) => packet._data;
-}
-
-
///
/// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points.
/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
@@ -9633,101 +9592,6 @@ public ushort Ability
}
-///
-/// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points.
-/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
-///
-public readonly struct MaximumManaAndAbilityExtended
-{
- private readonly Memory _data;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- public MaximumManaAndAbilityExtended(Memory data)
- : this(data, true)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- /// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumManaAndAbilityExtended(Memory data, bool initialize)
- {
- this._data = data;
- if (initialize)
- {
- var header = this.Header;
- header.Type = HeaderType;
- header.Code = Code;
- header.Length = (byte)Math.Min(data.Length, Length);
- header.SubCode = SubCode;
- }
- }
-
- ///
- /// Gets the header type of this data packet.
- ///
- public static byte HeaderType => 0xC1;
-
- ///
- /// Gets the operation code of this data packet.
- ///
- public static byte Code => 0x27;
-
- ///
- /// Gets the operation sub-code of this data packet.
- /// The is used as a grouping key.
- ///
- public static byte SubCode => 0xFE;
-
- ///
- /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
- ///
- public static int Length => 12;
-
- ///
- /// Gets the header of this packet.
- ///
- public C1HeaderWithSubCode Header => new (this._data);
-
- ///
- /// Gets or sets the mana.
- ///
- public uint Mana
- {
- get => ReadUInt32LittleEndian(this._data.Span[4..]);
- set => WriteUInt32LittleEndian(this._data.Span[4..], value);
- }
-
- ///
- /// Gets or sets the ability.
- ///
- public uint Ability
- {
- get => ReadUInt32LittleEndian(this._data.Span[8..]);
- set => WriteUInt32LittleEndian(this._data.Span[8..], value);
- }
-
- ///
- /// Performs an implicit conversion from a Memory of bytes to a .
- ///
- /// The packet as span.
- /// The packet as struct.
- public static implicit operator MaximumManaAndAbilityExtended(Memory packet) => new (packet, false);
-
- ///
- /// Performs an implicit conversion from to a Memory of bytes.
- ///
- /// The packet as struct.
- /// The packet as byte span.
- public static implicit operator Memory(MaximumManaAndAbilityExtended packet) => packet._data;
-}
-
-
///
/// Is sent by the server when: The item has been removed from the inventory of the player.
/// Causes reaction on client side: The client removes the item in the inventory user interface.
@@ -9906,8 +9770,8 @@ public ConsumeItemWithEffect.ConsumedItemType ItemType
///
public ushort EffectTimeInSeconds
{
- get => ReadUInt16BigEndian(this._data.Span[4..]);
- set => WriteUInt16BigEndian(this._data.Span[4..], value);
+ get => ReadUInt16LittleEndian(this._data.Span[4..]);
+ set => WriteUInt16LittleEndian(this._data.Span[4..], value);
}
///
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
index 3a93efe10..4aa40c1aa 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
@@ -3168,21 +3168,21 @@
C1HeaderWithSubCode
26
- FF
- CurrentHealthAndShieldExtended
- 12
+ FE
+ MaximumHealthAndShield
+ 9
ServerToClient
- Periodically, or if the current health or shield changed on the server side, e.g. by hits.
+ When the maximum health changed, e.g. by adding stat points or changed items.
The health and shield bar is updated on the game client user interface.
4
- IntegerLittleEndian
+ ShortBigEndian
Health
- 8
- IntegerLittleEndian
+ 7
+ ShortBigEndian
Shield
@@ -3190,34 +3190,54 @@
C1HeaderWithSubCode
26
- FE
- MaximumHealthAndShield
- 9
+ FF
+ CurrentStatsExtended
+ 24
ServerToClient
- When the maximum health changed, e.g. by adding stat points or changed items.
- The health and shield bar is updated on the game client user interface.
+ Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits.
+ The values are updated on the game client user interface.
4
- ShortBigEndian
+ IntegerLittleEndian
Health
- 7
- ShortBigEndian
+ 8
+ IntegerLittleEndian
Shield
+
+ 12
+ IntegerLittleEndian
+ Mana
+
+
+ 16
+ IntegerLittleEndian
+ Ability
+
+
+ 20
+ ShortLittleEndian
+ AttackSpeed
+
+
+ 22
+ ShortLittleEndian
+ MagicSpeed
+
C1HeaderWithSubCode
26
FE
- MaximumHealthAndShieldExtended
- 12
+ MaximumStatsExtended
+ 20
ServerToClient
- When the maximum health changed, e.g. by adding stat points or changed items.
- The health and shield bar is updated on the game client user interface.
+ When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items.
+ The values are updated on the game client user interface.
4
@@ -3229,6 +3249,16 @@
IntegerLittleEndian
Shield
+
+ 12
+ IntegerLittleEndian
+ Mana
+
+
+ 16
+ IntegerLittleEndian
+ Ability
+
@@ -3297,28 +3327,6 @@
-
- C1HeaderWithSubCode
- 27
- FF
- CurrentManaAndAbilityExtended
- 12
- ServerToClient
- The currently available mana or ability has changed, e.g. by using a skill.
- The mana and ability bar is updated on the game client user interface.
-
-
- 4
- IntegerLittleEndian
- Mana
-
-
- 8
- IntegerLittleEndian
- Ability
-
-
-
C1HeaderWithSubCode
27
@@ -3341,28 +3349,6 @@
-
- C1HeaderWithSubCode
- 27
- FE
- MaximumManaAndAbilityExtended
- 12
- ServerToClient
- The maximum available mana or ability has changed, e.g. by adding stat points.
- The mana and ability bar is updated on the game client user interface.
-
-
- 4
- IntegerLittleEndian
- Mana
-
-
- 8
- IntegerLittleEndian
- Ability
-
-
-
C1Header
28
@@ -3403,7 +3389,7 @@
4
- ShortBigEndian
+ ShortLittleEndian
EffectTimeInSeconds
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
index 8f55d24bb..c272830c2 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
@@ -8668,28 +8668,28 @@ public ushort Shield
///
-/// Is sent by the server when: Periodically, or if the current health or shield changed on the server side, e.g. by hits.
+/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
///
-public readonly ref struct CurrentHealthAndShieldExtendedRef
+public readonly ref struct MaximumHealthAndShieldRef
{
private readonly Span _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public CurrentHealthAndShieldExtendedRef(Span data)
+ public MaximumHealthAndShieldRef(Span data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private CurrentHealthAndShieldExtendedRef(Span data, bool initialize)
+ private MaximumHealthAndShieldRef(Span data, bool initialize)
{
this._data = data;
if (initialize)
@@ -8716,12 +8716,12 @@ private CurrentHealthAndShieldExtendedRef(Span data, bool initialize)
/// Gets the operation sub-code of this data packet.
/// The is used as a grouping key.
///
- public static byte SubCode => 0xFF;
+ public static byte SubCode => 0xFE;
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 12;
+ public static int Length => 9;
///
/// Gets the header of this packet.
@@ -8731,60 +8731,60 @@ private CurrentHealthAndShieldExtendedRef(Span data, bool initialize)
///
/// Gets or sets the health.
///
- public uint Health
+ public ushort Health
{
- get => ReadUInt32LittleEndian(this._data[4..]);
- set => WriteUInt32LittleEndian(this._data[4..], value);
+ get => ReadUInt16BigEndian(this._data[4..]);
+ set => WriteUInt16BigEndian(this._data[4..], value);
}
///
/// Gets or sets the shield.
///
- public uint Shield
+ public ushort Shield
{
- get => ReadUInt32LittleEndian(this._data[8..]);
- set => WriteUInt32LittleEndian(this._data[8..], value);
+ get => ReadUInt16BigEndian(this._data[7..]);
+ set => WriteUInt16BigEndian(this._data[7..], value);
}
///
- /// Performs an implicit conversion from a Span of bytes to a .
+ /// Performs an implicit conversion from a Span of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator CurrentHealthAndShieldExtendedRef(Span packet) => new (packet, false);
+ public static implicit operator MaximumHealthAndShieldRef(Span packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Span of bytes.
+ /// Performs an implicit conversion from to a Span of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Span(CurrentHealthAndShieldExtendedRef packet) => packet._data;
+ public static implicit operator Span(MaximumHealthAndShieldRef packet) => packet._data;
}
///
-/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
-/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+/// Is sent by the server when: Periodically, or if the current stats, like health, shield, mana or attack speed changed on the server side, e.g. by hits.
+/// Causes reaction on client side: The values are updated on the game client user interface.
///
-public readonly ref struct MaximumHealthAndShieldRef
+public readonly ref struct CurrentStatsExtendedRef
{
private readonly Span _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public MaximumHealthAndShieldRef(Span data)
+ public CurrentStatsExtendedRef(Span data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumHealthAndShieldRef(Span data, bool initialize)
+ private CurrentStatsExtendedRef(Span data, bool initialize)
{
this._data = data;
if (initialize)
@@ -8811,12 +8811,12 @@ private MaximumHealthAndShieldRef(Span data, bool initialize)
/// Gets the operation sub-code of this data packet.
/// The is used as a grouping key.
///
- public static byte SubCode => 0xFE;
+ public static byte SubCode => 0xFF;
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 9;
+ public static int Length => 24;
///
/// Gets the header of this packet.
@@ -8826,60 +8826,96 @@ private MaximumHealthAndShieldRef(Span data, bool initialize)
///
/// Gets or sets the health.
///
- public ushort Health
+ public uint Health
{
- get => ReadUInt16BigEndian(this._data[4..]);
- set => WriteUInt16BigEndian(this._data[4..], value);
+ get => ReadUInt32LittleEndian(this._data[4..]);
+ set => WriteUInt32LittleEndian(this._data[4..], value);
}
///
/// Gets or sets the shield.
///
- public ushort Shield
+ public uint Shield
{
- get => ReadUInt16BigEndian(this._data[7..]);
- set => WriteUInt16BigEndian(this._data[7..], value);
+ get => ReadUInt32LittleEndian(this._data[8..]);
+ set => WriteUInt32LittleEndian(this._data[8..], value);
}
///
- /// Performs an implicit conversion from a Span of bytes to a .
+ /// Gets or sets the mana.
+ ///
+ public uint Mana
+ {
+ get => ReadUInt32LittleEndian(this._data[12..]);
+ set => WriteUInt32LittleEndian(this._data[12..], value);
+ }
+
+ ///
+ /// Gets or sets the ability.
+ ///
+ public uint Ability
+ {
+ get => ReadUInt32LittleEndian(this._data[16..]);
+ set => WriteUInt32LittleEndian(this._data[16..], value);
+ }
+
+ ///
+ /// Gets or sets the attack speed.
+ ///
+ public ushort AttackSpeed
+ {
+ get => ReadUInt16LittleEndian(this._data[20..]);
+ set => WriteUInt16LittleEndian(this._data[20..], value);
+ }
+
+ ///
+ /// Gets or sets the magic speed.
+ ///
+ public ushort MagicSpeed
+ {
+ get => ReadUInt16LittleEndian(this._data[22..]);
+ set => WriteUInt16LittleEndian(this._data[22..], value);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Span of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator MaximumHealthAndShieldRef(Span packet) => new (packet, false);
+ public static implicit operator CurrentStatsExtendedRef(Span packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Span of bytes.
+ /// Performs an implicit conversion from to a Span of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Span(MaximumHealthAndShieldRef packet) => packet._data;
+ public static implicit operator Span(CurrentStatsExtendedRef packet) => packet._data;
}
///
-/// Is sent by the server when: When the maximum health changed, e.g. by adding stat points or changed items.
-/// Causes reaction on client side: The health and shield bar is updated on the game client user interface.
+/// Is sent by the server when: When the maximum stats, like health, shield, mana or attack speed changed on the server side, e.g. by adding stat points or changed items.
+/// Causes reaction on client side: The values are updated on the game client user interface.
///
-public readonly ref struct MaximumHealthAndShieldExtendedRef
+public readonly ref struct MaximumStatsExtendedRef
{
private readonly Span _data;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
- public MaximumHealthAndShieldExtendedRef(Span data)
+ public MaximumStatsExtendedRef(Span data)
: this(data, true)
{
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The underlying data.
/// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumHealthAndShieldExtendedRef(Span data, bool initialize)
+ private MaximumStatsExtendedRef(Span data, bool initialize)
{
this._data = data;
if (initialize)
@@ -8911,7 +8947,7 @@ private MaximumHealthAndShieldExtendedRef(Span data, bool initialize)
///
/// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
///
- public static int Length => 12;
+ public static int Length => 20;
///
/// Gets the header of this packet.
@@ -8937,18 +8973,36 @@ public uint Shield
}
///
- /// Performs an implicit conversion from a Span of bytes to a .
+ /// Gets or sets the mana.
+ ///
+ public uint Mana
+ {
+ get => ReadUInt32LittleEndian(this._data[12..]);
+ set => WriteUInt32LittleEndian(this._data[12..], value);
+ }
+
+ ///
+ /// Gets or sets the ability.
+ ///
+ public uint Ability
+ {
+ get => ReadUInt32LittleEndian(this._data[16..]);
+ set => WriteUInt32LittleEndian(this._data[16..], value);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Span of bytes to a .
///
/// The packet as span.
/// The packet as struct.
- public static implicit operator MaximumHealthAndShieldExtendedRef(Span packet) => new (packet, false);
+ public static implicit operator MaximumStatsExtendedRef(Span packet) => new (packet, false);
///
- /// Performs an implicit conversion from to a Span of bytes.
+ /// Performs an implicit conversion from to a Span of bytes.
///
/// The packet as struct.
/// The packet as byte span.
- public static implicit operator Span(MaximumHealthAndShieldExtendedRef packet) => packet._data;
+ public static implicit operator Span(MaximumStatsExtendedRef packet) => packet._data;
}
@@ -9237,101 +9291,6 @@ public ushort Ability
}
-///
-/// Is sent by the server when: The currently available mana or ability has changed, e.g. by using a skill.
-/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
-///
-public readonly ref struct CurrentManaAndAbilityExtendedRef
-{
- private readonly Span _data;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- public CurrentManaAndAbilityExtendedRef(Span data)
- : this(data, true)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- /// If set to true, the header data is automatically initialized and written to the underlying span.
- private CurrentManaAndAbilityExtendedRef(Span data, bool initialize)
- {
- this._data = data;
- if (initialize)
- {
- var header = this.Header;
- header.Type = HeaderType;
- header.Code = Code;
- header.Length = (byte)Math.Min(data.Length, Length);
- header.SubCode = SubCode;
- }
- }
-
- ///
- /// Gets the header type of this data packet.
- ///
- public static byte HeaderType => 0xC1;
-
- ///
- /// Gets the operation code of this data packet.
- ///
- public static byte Code => 0x27;
-
- ///
- /// Gets the operation sub-code of this data packet.
- /// The is used as a grouping key.
- ///
- public static byte SubCode => 0xFF;
-
- ///
- /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
- ///
- public static int Length => 12;
-
- ///
- /// Gets the header of this packet.
- ///
- public C1HeaderWithSubCodeRef Header => new (this._data);
-
- ///
- /// Gets or sets the mana.
- ///
- public uint Mana
- {
- get => ReadUInt32LittleEndian(this._data[4..]);
- set => WriteUInt32LittleEndian(this._data[4..], value);
- }
-
- ///
- /// Gets or sets the ability.
- ///
- public uint Ability
- {
- get => ReadUInt32LittleEndian(this._data[8..]);
- set => WriteUInt32LittleEndian(this._data[8..], value);
- }
-
- ///
- /// Performs an implicit conversion from a Span of bytes to a .
- ///
- /// The packet as span.
- /// The packet as struct.
- public static implicit operator CurrentManaAndAbilityExtendedRef(Span packet) => new (packet, false);
-
- ///
- /// Performs an implicit conversion from to a Span of bytes.
- ///
- /// The packet as struct.
- /// The packet as byte span.
- public static implicit operator Span(CurrentManaAndAbilityExtendedRef packet) => packet._data;
-}
-
-
///
/// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points.
/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
@@ -9427,101 +9386,6 @@ public ushort Ability
}
-///
-/// Is sent by the server when: The maximum available mana or ability has changed, e.g. by adding stat points.
-/// Causes reaction on client side: The mana and ability bar is updated on the game client user interface.
-///
-public readonly ref struct MaximumManaAndAbilityExtendedRef
-{
- private readonly Span _data;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- public MaximumManaAndAbilityExtendedRef(Span data)
- : this(data, true)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The underlying data.
- /// If set to true, the header data is automatically initialized and written to the underlying span.
- private MaximumManaAndAbilityExtendedRef(Span data, bool initialize)
- {
- this._data = data;
- if (initialize)
- {
- var header = this.Header;
- header.Type = HeaderType;
- header.Code = Code;
- header.Length = (byte)Math.Min(data.Length, Length);
- header.SubCode = SubCode;
- }
- }
-
- ///
- /// Gets the header type of this data packet.
- ///
- public static byte HeaderType => 0xC1;
-
- ///
- /// Gets the operation code of this data packet.
- ///
- public static byte Code => 0x27;
-
- ///
- /// Gets the operation sub-code of this data packet.
- /// The is used as a grouping key.
- ///
- public static byte SubCode => 0xFE;
-
- ///
- /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed.
- ///
- public static int Length => 12;
-
- ///
- /// Gets the header of this packet.
- ///
- public C1HeaderWithSubCodeRef Header => new (this._data);
-
- ///
- /// Gets or sets the mana.
- ///
- public uint Mana
- {
- get => ReadUInt32LittleEndian(this._data[4..]);
- set => WriteUInt32LittleEndian(this._data[4..], value);
- }
-
- ///
- /// Gets or sets the ability.
- ///
- public uint Ability
- {
- get => ReadUInt32LittleEndian(this._data[8..]);
- set => WriteUInt32LittleEndian(this._data[8..], value);
- }
-
- ///
- /// Performs an implicit conversion from a Span of bytes to a .
- ///
- /// The packet as span.
- /// The packet as struct.
- public static implicit operator MaximumManaAndAbilityExtendedRef(Span packet) => new (packet, false);
-
- ///
- /// Performs an implicit conversion from to a Span of bytes.
- ///
- /// The packet as struct.
- /// The packet as byte span.
- public static implicit operator Span(MaximumManaAndAbilityExtendedRef packet) => packet._data;
-}
-
-
///
/// Is sent by the server when: The item has been removed from the inventory of the player.
/// Causes reaction on client side: The client removes the item in the inventory user interface.
@@ -9679,8 +9543,8 @@ public ConsumeItemWithEffect.ConsumedItemType ItemType
///
public ushort EffectTimeInSeconds
{
- get => ReadUInt16BigEndian(this._data[4..]);
- set => WriteUInt16BigEndian(this._data[4..], value);
+ get => ReadUInt16LittleEndian(this._data[4..]);
+ set => WriteUInt16LittleEndian(this._data[4..], value);
}
///
diff --git a/src/Persistence/EntityFramework/Migrations/20241018200704_AddAttributeMaximum.Designer.cs b/src/Persistence/EntityFramework/Migrations/20241018200704_AddAttributeMaximum.Designer.cs
new file mode 100644
index 000000000..12e4a0705
--- /dev/null
+++ b/src/Persistence/EntityFramework/Migrations/20241018200704_AddAttributeMaximum.Designer.cs
@@ -0,0 +1,5008 @@
+//
+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("20241018200704_AddAttributeMaximum")]
+ partial class AddAttributeMaximum
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .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("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.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("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("TargetAttributeId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("InputAttributeId");
+
+ b.HasIndex("OperandAttributeId");
+
+ b.HasIndex("PowerUpDefinitionValueId");
+
+ 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")
+ .IsRequired()
+ .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("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("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