Skip to content

Commit

Permalink
Merge pull request #502 from MUnique/dev/attack-speed-stat
Browse files Browse the repository at this point in the history
Attack speed calculation
  • Loading branch information
sven-n authored Oct 29, 2024
2 parents b48093e + 64e0fe3 commit 1d4ef62
Show file tree
Hide file tree
Showing 77 changed files with 6,622 additions and 1,372 deletions.
Original file line number Diff line number Diff line change
@@ -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 |
| 8 | 4 | IntegerLittleEndian | | Shield |
| 12 | 4 | IntegerLittleEndian | | Mana |
| 16 | 4 | IntegerLittleEndian | | Ability |
20 changes: 0 additions & 20 deletions docs/Packets/C1-26-FF-CurrentHealthAndShieldExtended_by-server.md

This file was deleted.

24 changes: 24 additions & 0 deletions docs/Packets/C1-26-FF-CurrentStatsExtended_by-server.md
Original file line number Diff line number Diff line change
@@ -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 |
20 changes: 0 additions & 20 deletions docs/Packets/C1-27-FE-MaximumManaAndAbilityExtended_by-server.md

This file was deleted.

20 changes: 0 additions & 20 deletions docs/Packets/C1-27-FF-CurrentManaAndAbilityExtended_by-server.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/Packets/C3-29-ConsumeItemWithEffect_by-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions docs/Packets/ServerToClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 5 additions & 6 deletions src/AttributeSystem/AttributeDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,18 @@ public AttributeDefinition(Guid id, string designation, string description)
/// <summary>
/// Gets or sets the designation.
/// </summary>
/// <value>
/// The designation.
/// </value>
public string? Designation { get; set; }

/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public string? Description { get; set; }

/// <summary>
/// Gets or sets the maximum value of this attribute, if the value should be capped.
/// </summary>
public float? MaximumValue { get; set; }

/// <summary>
/// Implements the operator ==.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/AttributeSystem/AttributeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
8 changes: 7 additions & 1 deletion src/AttributeSystem/ComposableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
10 changes: 9 additions & 1 deletion src/AttributeSystem/StatAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ public StatAttribute(AttributeDefinition definition, float baseValue)
/// </value>
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
{
Expand Down
11 changes: 5 additions & 6 deletions src/GameLogic/AttackableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,13 @@ public static async ValueTask ApplyRegenerationAsync(this IAttackable target, Pl

if (target is IWorldObserver observer)
{
if (isHealthUpdated)
{
await observer.InvokeViewPlugInAsync<IUpdateCurrentHealthPlugIn>(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<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await observer.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync(updatedStats)).ConfigureAwait(false);
}
}
}
Expand Down
33 changes: 32 additions & 1 deletion src/GameLogic/Attributes/Stats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,38 @@ public class Stats
/// <summary>
/// Gets the attack speed attribute definition.
/// </summary>
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,
};

/// <summary>
/// Gets the attack speed by weapon attribute definition.
/// </summary>
public static AttributeDefinition AttackSpeedByWeapon { get; } = new(new Guid("45EEEDEE-C76B-40E6-A0BC-2B493E10B140"), "Attack Speed by Weapons", string.Empty);

/// <summary>
/// Gets the attribute which says, if two weapons are equipped.
/// </summary>
public static AttributeDefinition AreTwoWeaponsEquipped { get; } = new(new Guid("56DA895D-BAFD-4A5C-9864-B17AB8369998"), "Are two weapons equipped", string.Empty)
{
MaximumValue = 1,
};

/// <summary>
/// Gets the attribute which counts the equipped weapons.
/// </summary>
public static AttributeDefinition EquippedWeaponCount { get; } = new(new Guid("15D6493F-549D-455F-9FFF-A0D589FD7DA2"), "Equipped Weapon Count", string.Empty);

/// <summary>
/// Gets the magic speed attribute definition which is used for some skills.
/// </summary>
public static AttributeDefinition MagicSpeed { get; } = new(new Guid("AE32AA45-9C18-43B3-9F7B-648FD7F4B0AD"), "Magic Speed", string.Empty);

/// <summary>
/// Gets the walk speed attribute definition.
/// </summary>
public static AttributeDefinition WalkSpeed { get; } = new(new Guid("9CDDC598-E5F3-4372-9294-505455E4A40B"), "Walk Speed", string.Empty);

/// <summary>
/// Gets the attack damage increase attribute definition.
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/ItemConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MUnique.OpenMU.GameLogic;
/// <summary>
/// A central place to keep item identifiers, so we can keep track of them.
/// </summary>
internal class ItemConstants
public class ItemConstants
{
/// <summary>
/// Gets the identifier for the summon orb.
Expand Down
38 changes: 27 additions & 11 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MUnique.OpenMU.GameLogic;

using System;
using System.Threading;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
Expand All @@ -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;

/// <summary>
/// The base implementation of a player.
Expand Down Expand Up @@ -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<IUpdateCurrentHealthPlugIn>(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(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<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
}

await this.HitAsync(hitInfo, attacker, skill?.Skill).ConfigureAwait(false);
Expand Down Expand Up @@ -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<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateCurrentHealthPlugIn>(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync()).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -1229,8 +1230,7 @@ public async Task RegenerateAsync()
attributes[r.MaximumAttribute]);
}

await this.InvokeViewPlugInAsync<IUpdateCurrentHealthPlugIn>(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync()).ConfigureAwait(false);

await this.RegenerateHeroStateAsync().ConfigureAwait(false);
}
Expand Down Expand Up @@ -1339,7 +1339,8 @@ public async ValueTask<bool> TryConsumeForSkillAsync(Skill skill)
this.Attributes![requirement.Attribute] -= this.GetRequiredValue(requirement);
}

await this.InvokeViewPlugInAsync<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);

return true;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<IUpdateMaximumHealthPlugIn>(p => p.UpdateMaximumHealthAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateCurrentHealthPlugIn>(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false);

await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateMaximumStatsAsync(UpdatedStats.Health)).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync(UpdatedStats.Health)).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -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<IUpdateMaximumManaPlugIn>(p => p.UpdateMaximumManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateCurrentManaPlugIn>(p => p.UpdateCurrentManaAsync()).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(p => p.UpdateMaximumStatsAsync(UpdatedStats.Mana)).ConfigureAwait(false);
await this.InvokeViewPlugInAsync<IUpdateStatsPlugIn>(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<IUpdateStatsPlugIn>(p => p.UpdateCurrentStatsAsync(UpdatedStats.Speed)).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down
Loading

0 comments on commit 1d4ef62

Please sign in to comment.