diff --git a/src/AttributeSystem/ComposableAttribute.cs b/src/AttributeSystem/ComposableAttribute.cs index 90183b6fa..1d5cd033f 100644 --- a/src/AttributeSystem/ComposableAttribute.cs +++ b/src/AttributeSystem/ComposableAttribute.cs @@ -68,18 +68,10 @@ private float GetAndCacheValue() var maxValues = this.Elements.Where(e => e.AggregateType == AggregateType.Maximum).MaxBy(e => e.Value)?.Value ?? 0; rawValues += maxValues; - if (multiValues == 0 && this.Elements.All(e => e.AggregateType != AggregateType.Multiplicate)) - { - multiValues = 1; - } - else if (rawValues == 0 && multiValues != 0 && this.Elements.All(e => e.AggregateType != AggregateType.AddRaw)) + if (this.Elements.All(e => e.AggregateType == AggregateType.Multiplicate)) { rawValues = 1; } - else - { - // nothing to do - } var newValue = (rawValues * multiValues) + finalValues; if (this._maximumValue.HasValue) diff --git a/src/GameLogic/AttackableExtensions.cs b/src/GameLogic/AttackableExtensions.cs index 5a4377795..12a601544 100644 --- a/src/GameLogic/AttackableExtensions.cs +++ b/src/GameLogic/AttackableExtensions.cs @@ -21,6 +21,8 @@ public static class AttackableExtensions { private const short ExplosionMagicEffectNumber = 75; // 0x4B + private const short StunnedMagicEffectNumber = 61; // 0x3D + private static readonly IDictionary ReductionModifiers = new Dictionary { @@ -28,6 +30,8 @@ public static class AttackableExtensions { Stats.CurrentAbility, Stats.AbilityUsageReduction }, }; + private static MagicEffectDefinition? stunEffectDefinition; + extension(IAttackable attackable) { /// @@ -327,7 +331,7 @@ public static async ValueTask ApplyMagicEffectAsync(this IAttackable target, IAt } float chance = target is Player ? skillEntry.PowerUpChancePvp!.Value : skillEntry.PowerUpChance!.Value; - if (!Rand.NextRandomBool(Convert.ToDouble(chance))) + if (!Rand.NextRandomBool(chance)) { return; } @@ -597,6 +601,20 @@ public static double CalculateBaseExperience(this IAttackable killedObject, floa return Math.Max(tempExperience, 0) * 1.25; } + /// + /// Applies the mace mastery stun effect to the specified attackable. + /// + /// The attacker. + /// The attackable. + /// A task representing the asynchronous operation. + public static async ValueTask ApplyMaceMasteryStunEffectAsync(this Player attacker, IAttackable attackable) + { + stunEffectDefinition ??= attacker.GameContext.Configuration.MagicEffects.First(m => m.Number == StunnedMagicEffectNumber); + var powerUp = attackable.Attributes.CreateElement(stunEffectDefinition.PowerUpDefinitions.First(pu => pu.TargetAttribute == Stats.IsStunned)); + var magicEffect = new MagicEffect(TimeSpan.FromSeconds(2), stunEffectDefinition, [new MagicEffect.ElementWithTarget(powerUp, Stats.IsStunned)]); + await attackable.MagicEffectList.AddEffectAsync(magicEffect).ConfigureAwait(false); + } + private static bool IsAttackSuccessfulTo(this IAttacker attacker, IAttackable defender) { var hitChance = attacker.GetHitChanceTo(defender); @@ -863,8 +881,8 @@ private static async ValueTask ApplyMagicEffectAsync(this IAttackable target, IA } else if (magicEffectDefinition.PowerUpDefinitions.Any(e => e.TargetAttribute == Stats.IsStunned)) { - var stunChancePowerUp = powerUps.FirstOrDefault(p => p.Target == Stats.StunChance); - if (stunChancePowerUp.Boost is null || !Rand.NextRandomBool(Convert.ToDouble(stunChancePowerUp.Boost.Value))) + var stunChancePowerUp = powerUps.FirstOrDefault(p => p.Target == Stats.MasteryStunChance); + if (stunChancePowerUp.Boost is null || !Rand.NextRandomBool(stunChancePowerUp.Boost.Value)) { return; } diff --git a/src/GameLogic/Attributes/PowerUpWrapper.cs b/src/GameLogic/Attributes/PowerUpWrapper.cs index 7e50e2b93..481acdde0 100644 --- a/src/GameLogic/Attributes/PowerUpWrapper.cs +++ b/src/GameLogic/Attributes/PowerUpWrapper.cs @@ -14,6 +14,8 @@ public sealed class PowerUpWrapper : IElement, IDisposable { private readonly IElement _element; + private readonly AggregateType? _aggregateType; + private ComposableAttribute? _parentAttribute; /// @@ -22,7 +24,8 @@ public sealed class PowerUpWrapper : IElement, IDisposable /// The element. /// The target attribute. /// The attribute holder. - public PowerUpWrapper(IElement element, AttributeDefinition targetAttribute, AttributeSystem attributeHolder) + /// The aggregate type that should override the 's. + public PowerUpWrapper(IElement element, AttributeDefinition targetAttribute, AttributeSystem attributeHolder, AggregateType? aggregateType = null) { this._parentAttribute = attributeHolder.GetComposableAttribute(targetAttribute); if (this._parentAttribute is null) @@ -31,6 +34,7 @@ public PowerUpWrapper(IElement element, AttributeDefinition targetAttribute, Att } this._element = element; + this._aggregateType = aggregateType; this._parentAttribute.AddElement(this); this._element.ValueChanged += this.OnValueChanged; } @@ -42,33 +46,36 @@ public PowerUpWrapper(IElement element, AttributeDefinition targetAttribute, Att public float Value => this._element.Value; /// - public AggregateType AggregateType => this._element.AggregateType; + public AggregateType AggregateType => this._aggregateType ?? this._element.AggregateType; /// /// Creates elements by a . /// /// The power up definition. /// The attribute holder. + /// The specific aggregate type. If not specified, the aggregate type of the 's constant value will be used. /// The elements which represent the power-up. - public static IEnumerable CreateByPowerUpDefinition(PowerUpDefinition powerUpDef, AttributeSystem attributeHolder) + public static IEnumerable CreateByPowerUpDefinition(PowerUpDefinition powerUpDef, AttributeSystem attributeHolder, AggregateType? aggregateType = null) { if (powerUpDef.Boost?.ConstantValue != null) { yield return new PowerUpWrapper( powerUpDef.Boost.ConstantValue, powerUpDef.TargetAttribute ?? throw Error.NotInitializedProperty(powerUpDef, nameof(PowerUpDefinition.TargetAttribute)), - attributeHolder); + attributeHolder, + aggregateType); } if (powerUpDef.Boost?.RelatedValues != null) { foreach (var relationship in powerUpDef.Boost.RelatedValues) { - var aggregateType = powerUpDef.Boost?.ConstantValue.AggregateType ?? AggregateType.AddRaw; + var aggregType = powerUpDef.Boost?.ConstantValue.AggregateType ?? AggregateType.AddRaw; yield return new PowerUpWrapper( - attributeHolder.CreateRelatedAttribute(relationship, attributeHolder, aggregateType), + attributeHolder.CreateRelatedAttribute(relationship, attributeHolder, aggregType), powerUpDef.TargetAttribute ?? throw Error.NotInitializedProperty(powerUpDef, nameof(PowerUpDefinition.TargetAttribute)), - attributeHolder); + attributeHolder, + aggregateType); } } } @@ -88,7 +95,7 @@ public void Dispose() /// public override string? ToString() { - return $"{this._parentAttribute?.Definition.Designation}: {this._element}"; + return $"{this._parentAttribute?.Definition.Designation}: {this._element}{(this._aggregateType is { } aggreg ? $"->({aggreg})" : string.Empty)}"; } private void OnValueChanged(object? sender, EventArgs eventArgs) diff --git a/src/GameLogic/Attributes/Stats.cs b/src/GameLogic/Attributes/Stats.cs index 77b6ddb9e..394c5a9fe 100644 --- a/src/GameLogic/Attributes/Stats.cs +++ b/src/GameLogic/Attributes/Stats.cs @@ -243,6 +243,14 @@ public class Stats /// /// Gets the min and max physical base DMG attribute definition. /// + /// + /// values include: + /// Weapon item option; excellent lvl/20 option (weapons) + /// values include: + /// Double wield halving (averaging). + /// values include: + /// Excellent lvl/20 option (pendant); wings damage option; harmony damage (min and max) option; gold fenrir damage bonus; . + /// public static AttributeDefinition PhysicalBaseDmg { get; } = new(new Guid("DD1E13E4-BFFD-45B5-9B91-9080710324B2"), "Physical Base Damage (min and max)", string.Empty); /// @@ -421,7 +429,12 @@ public class Stats /// /// Gets the physical base (min and max) damage increase attribute definition>. /// - /// Includes excellent 2% physical increase option, ammunition damage increase, and the double wield multiplier (55%). + /// + /// values include: + /// Excellent 2% increase option (double wield weapons); ammunition damage increase. + /// values include: + /// Double wield halving (averaging); double wield bonus multiplier (55%); excellent 2% increase option (pendant and other weapons). + /// public static AttributeDefinition PhysicalBaseDmgIncrease { get; } = new(new Guid("104B4DAA-C507-4CBB-AF38-D53DDBB4817E"), "Physical Base Damage Increase", string.Empty); /// @@ -1130,14 +1143,51 @@ public class Stats public static AttributeDefinition DoubleDamageChance { get; } = new(new Guid("2B8A03E6-1CC2-48A0-8633-3F36E17050F4"), "Double Damage Chance", string.Empty); /// - /// Gets the stun chance attribute definition. + /// Gets the MST stun chance attribute definition. + /// + /// Bucket attribute for the master skills: wind tome (book of neil) mastery, fire burst mastery, earthshake mastery and mace mastery. + public static AttributeDefinition MasteryStunChance { get; } = new(new Guid("610D3259-1158-424A-8738-9EB7A71DE600"), "Mastery Stun Chance (MST)", "A generic master tree stun chance attribute, which serves as a bucket for \"mastery\" skills."); + + /// + /// Gets the MST mace mastery stun chance attribute definition. + /// + public static AttributeDefinition MaceMasteryStunChance { get; } = new(new Guid("6E3A9F2D-5B7C-4D8E-A1F3-2C9E5B7D4F6A"), "Mace Mastery Stun Chance (MST)", string.Empty); + + /// + /// Gets the MST target move chance attribute definition. + /// + /// Bucket attribute for the master skills: lightning tome (book of lagle) mastery and twisting slash mastery. + public static AttributeDefinition MasteryMoveTargetChance { get; } = new(new Guid("6F9619FF-8B86-D011-B42D-00C04FC964FF"), "Mastery Move Target Chance (MST)", "A generic master tree move target chance attribute, which serves as a bucket for \"mastery\" skills."); + + /// + /// Gets the rageful blow mastery decrease durability MST chance attribute definition. + /// + public static AttributeDefinition RagefulBlowMasteryDurabilityDecChance { get; } = new(new Guid("2F8A5D3B-9C7E-4F1A-B6D2-8E3C5A7F9B1D"), "Rageful Blow Mastery Durability Decrease Chance (MST)", string.Empty); + + /// + /// Gets the durability reduction factor attribute definition. + /// + /// + /// Factor by which the maximum durability of armor items is multiplied when attacked by Rageful Blow and is successful. + /// Decreases with the level of the master skills DurabilityReduction1 or DurabilityReduction1FistMaster. + /// Value ranges from 10% (default) to 6%. + /// + public static AttributeDefinition DurabilityReductionFactor { get; } = new(new Guid("3C9E7F2A-B1D4-8E6F-5A0C-1D8F3B5E7A2D"), "Durability Reduction Factor", string.Empty); + + /// + /// Gets the spear mastery double damage MST chance attribute definition. + /// + public static AttributeDefinition SpearMasteryDoubleDamageChance { get; } = new(new Guid("5D9E2A7B-3C4F-8E1A-B5D6-2E7F9C4A1B8D"), "Spear Mastery Double Damage Chance (MST)", string.Empty); + + /// + /// Gets the swell life skill health increase attribute definition. /// - public static AttributeDefinition StunChance { get; } = new(new Guid("610D3259-1158-424A-8738-9EB7A71DE600"), "Stun Chance", string.Empty); + public static AttributeDefinition SwellLifeHealthIncrease { get; } = new(new Guid("9C4E7B2A-F1D6-4A3E-B8C5-1D7F2E9A3B4C"), "Swell Life Health Increase", string.Empty); /// - /// Gets the pollution skill MST target move chance, which rises with lightning tome mastery. + /// Gets the swell life skill mana increase attribute definition. /// - public static AttributeDefinition PollutionMoveTargetChance { get; } = new(new Guid("6F9619FF-8B86-D011-B42D-00C04FC964FF"), "Pollution Move Target Chance (MST)", "The pollution skill (book of lagle) move chance, which rises with lightning tome mastery."); + public static AttributeDefinition SwellLifeManaIncrease { get; } = new(new Guid("8B4F1C6D-9A2E-4F7B-A3D5-1E9C7F2A4B6D"), "Swell Life Mana Increase", string.Empty); /// /// Gets the mana after monster kill attribute definition. diff --git a/src/GameLogic/ItemExtensions.cs b/src/GameLogic/ItemExtensions.cs index 94deccaa5..889d0804c 100644 --- a/src/GameLogic/ItemExtensions.cs +++ b/src/GameLogic/ItemExtensions.cs @@ -149,6 +149,18 @@ public static bool IsJewelry(this Item item) return item.ItemSlot >= InventoryConstants.PendantSlot && item.ItemSlot <= InventoryConstants.Ring2Slot; } + /// + /// Determines whether this item is an armor item. + /// + /// The item. + /// + /// true if the specified item is armor; otherwise, false. + /// + public static bool IsArmorItem(this Item item) + { + return item.ItemSlot >= InventoryConstants.HelmSlot && item.ItemSlot <= InventoryConstants.BootsSlot; + } + /// /// Determines whether this instance is a is weapon which deals physical damage. /// diff --git a/src/GameLogic/ItemPowerUpFactory.cs b/src/GameLogic/ItemPowerUpFactory.cs index 1107bcef0..e4ad11e7f 100644 --- a/src/GameLogic/ItemPowerUpFactory.cs +++ b/src/GameLogic/ItemPowerUpFactory.cs @@ -273,7 +273,28 @@ private IEnumerable GetPowerUpsOfItemOptions(Item item, Attribut continue; } - foreach (var wrapper in PowerUpWrapper.CreateByPowerUpDefinition(powerUp, attributeHolder)) + AggregateType? aggregateType = null; + if (option.OptionType == ItemOptionTypes.Excellent) + { + if (item.ItemSlot == InventoryConstants.PendantSlot + && option.PowerUpDefinition?.TargetAttribute == Stats.PhysicalBaseDmg) + { + // Pendant options are not subject to double wield averaging + aggregateType = AggregateType.AddFinal; + } + else if (option.PowerUpDefinition?.TargetAttribute == Stats.PhysicalBaseDmgIncrease + && item.Definition!.BasePowerUpAttributes.Any(pu => pu.TargetAttribute == Stats.DoubleWieldWeaponCount)) + { + // This needs special treatment, since this option is averaged when double wielding + aggregateType = AggregateType.AddRaw; + } + else + { + // the normal aggregate type should be used + } + } + + foreach (var wrapper in PowerUpWrapper.CreateByPowerUpDefinition(powerUp, attributeHolder, aggregateType)) { yield return wrapper; } @@ -344,6 +365,13 @@ private IEnumerable CreateExcellentAndAncientBasePowerUpWrappers yield return new PowerUpWrapper(new SimpleElement(ancientBonus, AggregateType.AddRaw), minDmgAttribute, attributeHolder); yield return new PowerUpWrapper(new SimpleElement(ancientBonus, AggregateType.AddRaw), maxDmgAttribute, attributeHolder); } + + if (itemIsExcellent + && item.ItemOptions.Any(io => io.ItemOption?.PowerUpDefinition?.TargetAttribute == Stats.PhysicalBaseDmgIncrease) + && item.Definition!.BasePowerUpAttributes.Any(pu => pu.TargetAttribute == Stats.DoubleWieldWeaponCount)) + { + yield return new PowerUpWrapper(new SimpleElement(-1, AggregateType.AddRaw), Stats.PhysicalBaseDmgIncrease, attributeHolder); + } } if (item.IsWizardryWeapon(out var staffRise) && item.ItemSlot == InventoryConstants.LeftHandSlot) diff --git a/src/GameLogic/MapInitializer.cs b/src/GameLogic/MapInitializer.cs index ca5f975cd..9d2f38772 100644 --- a/src/GameLogic/MapInitializer.cs +++ b/src/GameLogic/MapInitializer.cs @@ -1,4 +1,4 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -88,7 +88,7 @@ public async ValueTask InitializeStateAsync(GameMap createdMap) _ = this.PlugInManager ?? throw new InvalidOperationException("PlugInManager must be set first"); _ = this.PathFinderPool ?? throw new InvalidOperationException("PathFinderPool must be set first"); - this._logger.LogDebug("Start creating monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Start creating monster instances for map {createdMap}", createdMap.Definition.Name); var automaticSpawns = createdMap.Definition.MonsterSpawns .Where(m => m.MonsterDefinition is not null) .Where(m => m.SpawnTrigger is SpawnTrigger.Automatic); @@ -117,7 +117,7 @@ public async ValueTask InitializeStateAsync(GameMap createdMap) this._spawnedMonsters.AddOrUpdate(spawnArea, spawnArea.Quantity, (_, _) => spawnArea.Quantity); }); - this._logger.LogDebug("Finished creating monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Finished creating monster instances for map {createdMap}", createdMap.Definition.Name); } /// @@ -130,7 +130,7 @@ public async ValueTask InitializeNpcsOnEventStartAsync(GameMap createdMap, IEven _ = this.PlugInManager ?? throw new InvalidOperationException("PlugInManager must be set first"); _ = this.PathFinderPool ?? throw new InvalidOperationException("PathFinderPool must be set first"); - this._logger.LogDebug("Start creating event monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Start creating event monster instances for map {createdMap}", createdMap.Definition.Name); var eventSpawns = createdMap.Definition.MonsterSpawns .Where(m => m.MonsterDefinition is not null) .Where(m => m.SpawnTrigger is SpawnTrigger.OnceAtEventStart or SpawnTrigger.AutomaticDuringEvent); @@ -143,7 +143,7 @@ public async ValueTask InitializeNpcsOnEventStartAsync(GameMap createdMap, IEven } } - this._logger.LogDebug("Finished creating event monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Finished creating event monster instances for map {createdMap}", createdMap.Definition.Name); } /// @@ -152,7 +152,7 @@ public async ValueTask InitializeNpcsOnWaveStartAsync(GameMap createdMap, IEvent _ = this.PlugInManager ?? throw new InvalidOperationException("PlugInManager must be set first"); _ = this.PathFinderPool ?? throw new InvalidOperationException("PathFinderPool must be set first"); - this._logger.LogDebug("Start creating event monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Start creating event monster instances for map {createdMap}", createdMap.Definition.Name); var waveSpawns = createdMap.Definition.MonsterSpawns .Where(m => m.MonsterDefinition is not null) .Where(m => m.SpawnTrigger is SpawnTrigger.AutomaticDuringWave or SpawnTrigger.OnceAtWaveStart) @@ -166,7 +166,7 @@ public async ValueTask InitializeNpcsOnWaveStartAsync(GameMap createdMap, IEvent } } - this._logger.LogDebug("Finished creating event monster instances for map {createdMap}", createdMap); + this._logger.LogDebug("Finished creating event monster instances for map {createdMap}", createdMap.Definition.Name); } /// diff --git a/src/GameLogic/NPC/AttackableNpcBase.cs b/src/GameLogic/NPC/AttackableNpcBase.cs index 3c5d186e4..f94dba966 100644 --- a/src/GameLogic/NPC/AttackableNpcBase.cs +++ b/src/GameLogic/NPC/AttackableNpcBase.cs @@ -127,6 +127,11 @@ public int Health if (attacker is Player player) { await player.AfterHitTargetAsync().ConfigureAwait(false); + + if (this.IsAlive && Rand.NextRandomBool(player.Attributes![Stats.MaceMasteryStunChance])) + { + await player.ApplyMaceMasteryStunEffectAsync(this).ConfigureAwait(false); + } } if (attacker as IPlayerSurrogate is { } playerSurrogate) diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index ed06901e0..274e4b5f0 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -1,4 +1,4 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -756,7 +756,7 @@ public ValueTask ShowBlueMessageAsync(string message) } await this.HitAsync(hitInfo, attacker, skill?.Skill, isFinalStreakHit).ConfigureAwait(false); - await this.DecreaseItemDurabilityAfterHitAsync(hitInfo).ConfigureAwait(false); + await this.DecreaseItemDurabilityAfterHitAsync(hitInfo, skill).ConfigureAwait(false); if (attacker as IPlayerSurrogate is { } playerSurrogate) { @@ -766,6 +766,11 @@ public ValueTask ShowBlueMessageAsync(string message) if (attacker is Player attackerPlayer) { await attackerPlayer.AfterHitTargetAsync().ConfigureAwait(false); + + if (this.IsAlive && Rand.NextRandomBool(attackerPlayer.Attributes![Stats.MaceMasteryStunChance])) + { + await attackerPlayer.ApplyMaceMasteryStunEffectAsync(this).ConfigureAwait(false); + } } return hitInfo; @@ -2659,12 +2664,24 @@ private async void OnAmmunitionAmountChanged(object? sender, EventArgs args) } } - private async ValueTask DecreaseItemDurabilityAfterHitAsync(HitInfo hitInfo) + private async ValueTask DecreaseItemDurabilityAfterHitAsync(HitInfo hitInfo, SkillEntry? skill) { - var randomArmorItem = this.Inventory?.EquippedItems.Where(ItemExtensions.IsDefensiveItem).SelectRandom(); - if (randomArmorItem is { }) + var randomDefensiveItem = this.Inventory?.EquippedItems.Where(ItemExtensions.IsDefensiveItem).SelectRandom(); + if (randomDefensiveItem is { }) + { + await this.DecreaseDefenseItemDurabilityAsync(randomDefensiveItem, hitInfo).ConfigureAwait(false); + } + + if (Rand.NextRandomBool(skill?.Attributes?[Stats.RagefulBlowMasteryDurabilityDecChance] ?? 0)) { - await this.DecreaseDefenseItemDurabilityAsync(randomArmorItem, hitInfo).ConfigureAwait(false); + var randomArmorItem = this.Inventory?.EquippedItems.Where(ItemExtensions.IsArmorItem).SelectRandom(); + if (randomArmorItem is { }) + { + if (randomArmorItem.DecreaseDurability(randomArmorItem.GetMaximumDurabilityOfOnePiece() * this.Attributes![Stats.DurabilityReductionFactor])) + { + await this.InvokeViewPlugInAsync(p => p.ItemDurabilityChangedAsync(randomArmorItem, false)).ConfigureAwait(false); + } + } } if (this.Inventory?.GetItem(InventoryConstants.PetSlot) is { Durability: > 0.0 } pet) diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs index 992abdcdd..6fee6234f 100644 --- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs +++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs @@ -370,11 +370,16 @@ private async ValueTask ApplySkillAsync(Player player, SkillEntry skillEntry, IA await target.AttackByAsync(player, skillEntry, isCombo, 1, hit == skill.NumberOfHitsPerAttack).ConfigureAwait(false); } - var baseSkill = skillEntry.GetBaseSkill(); - - if (player.GameContext.PlugInManager.GetStrategy(baseSkill.Number) is { } strategy) + if (player.GameContext.PlugInManager.GetStrategy(skillEntry.Skill.Number) is { } strategy) { await strategy.AfterTargetGotAttackedAsync(player, target, skillEntry, targetAreaCenter, hitInfo).ConfigureAwait(false); + return; + } + + var baseSkill = skillEntry.GetBaseSkill(); + if (player.GameContext.PlugInManager.GetStrategy(baseSkill.Number) is { } baseSkillStrategy) + { + await baseSkillStrategy.AfterTargetGotAttackedAsync(player, target, skillEntry, targetAreaCenter, hitInfo).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs index 926f1067a..be12dbedc 100644 --- a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs @@ -39,7 +39,6 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab direction = (Direction)Rand.NextInt(1, 9); } - var currentDistance = startingPoint.EuclideanDistanceTo(currentTarget); for (int i = 0; i < 3; i++) { var nextTarget = currentTarget.CalculateTargetPoint(direction); @@ -51,7 +50,6 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab } currentTarget = nextTarget; - currentDistance = startingPoint.EuclideanDistanceTo(currentTarget); } await movableTarget.MoveAsync(currentTarget).ConfigureAwait(false); diff --git a/src/GameLogic/PlayerActions/Skills/PollutionSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/PollutionSkillPlugIn.cs index 4aa4458aa..618d0b86f 100644 --- a/src/GameLogic/PlayerActions/Skills/PollutionSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/PollutionSkillPlugIn.cs @@ -28,7 +28,7 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab if (!target.IsAlive || target is not IMovable movableTarget || target.CurrentMap is not { } currentMap - || !Rand.NextRandomBool(Convert.ToDouble(attacker.Attributes[Stats.PollutionMoveTargetChance]))) + || !Rand.NextRandomBool(attacker.Attributes[Stats.MasteryMoveTargetChance])) { return; } diff --git a/src/GameLogic/PlayerActions/Skills/RequiemSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/RequiemSkillPlugIn.cs index f733d79f7..3250a808f 100644 --- a/src/GameLogic/PlayerActions/Skills/RequiemSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/RequiemSkillPlugIn.cs @@ -31,13 +31,13 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab { this._stunEffectDefinition ??= ((Player)attacker).GameContext.Configuration.MagicEffects.First(m => m.Number == StunnedMagicEffectNumber); - if (!target.IsAlive || !Rand.NextRandomBool(Convert.ToDouble(attacker.Attributes[Stats.StunChance]))) + if (!target.IsAlive || !Rand.NextRandomBool(attacker.Attributes[Stats.MasteryStunChance])) { return; } - var powerUp = attacker.Attributes.CreateElement(this._stunEffectDefinition.PowerUpDefinitions.First()); - var magicEffect = new MagicEffect(powerUp, this._stunEffectDefinition, TimeSpan.FromSeconds(3)); + var powerUp = attacker.Attributes.CreateElement(this._stunEffectDefinition.PowerUpDefinitions.First(pu => pu.TargetAttribute == Stats.IsStunned)); + var magicEffect = new MagicEffect(TimeSpan.FromSeconds(3), this._stunEffectDefinition, [new MagicEffect.ElementWithTarget(powerUp, Stats.IsStunned)]); await target.MagicEffectList.AddEffectAsync(magicEffect).ConfigureAwait(false); if (target is ISupportWalk walkSupporter && walkSupporter.IsWalking) diff --git a/src/GameLogic/PlayerActions/Skills/TwistingSlashMasterySkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/TwistingSlashMasterySkillPlugIn.cs new file mode 100644 index 000000000..7695a8ffb --- /dev/null +++ b/src/GameLogic/PlayerActions/Skills/TwistingSlashMasterySkillPlugIn.cs @@ -0,0 +1,59 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.GameLogic.Attributes; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.GameLogic.PlugIns; +using MUnique.OpenMU.Pathfinding; +using MUnique.OpenMU.PlugIns; + +/// +/// Handles the twisting slash mastery skill of the dark knight class. Based on a chance, it may push the targets 2 squares away from the attacker. +/// +[PlugIn] +[Display(Name = nameof(PlugInResources.TwistingSlashMasterySkillPlugIn_Name), Description = nameof(PlugInResources.TwistingSlashMasterySkillPlugIn_Description), ResourceType = typeof(PlugInResources))] +[Guid("9F4B2C1D-E7A6-4B3C-8D9E-0FAB12C3D4E5")] +public class TwistingSlashMasterySkillPlugIn : IAreaSkillPlugIn +{ + /// + public short Key => 332; + + /// + public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo) + { + if (!target.IsAlive + || target is not IMovable movableTarget + || target.CurrentMap is not { } currentMap + || !Rand.NextRandomBool(attacker.Attributes[Stats.MasteryMoveTargetChance])) + { + return; + } + + var startingPoint = attacker.Position; + var currentTarget = target.Position; + var direction = startingPoint.GetDirectionTo(currentTarget); + if (direction == Direction.Undefined) + { + direction = (Direction)Rand.NextInt(1, 9); + } + + for (int i = 0; i < 2; i++) + { + var nextTarget = currentTarget.CalculateTargetPoint(direction); + if (!currentMap.Terrain.WalkMap[nextTarget.X, nextTarget.Y] + || (target is NonPlayerCharacter && target.CurrentMap.Terrain.SafezoneMap[nextTarget.X, nextTarget.Y])) + { + // we don't want to push the target into a non-reachable area, through walls or monsters into the safe zone. + break; + } + + currentTarget = nextTarget; + } + + await movableTarget.MoveAsync(currentTarget).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/GameLogic/Properties/PlugInResources.Designer.cs b/src/GameLogic/Properties/PlugInResources.Designer.cs index 6e19694f5..fc7b64491 100644 --- a/src/GameLogic/Properties/PlugInResources.Designer.cs +++ b/src/GameLogic/Properties/PlugInResources.Designer.cs @@ -2825,6 +2825,24 @@ public static string TrackChatCommandPlugIn_Name { } } + /// + /// Looks up a localized string similar to Handles the twisting slash mastery skill of the dark knight class. Based on a chance, it may push the targets 2 squares away from the attacker.. + /// + public static string TwistingSlashMasterySkillPlugIn_Description { + get { + return ResourceManager.GetString("TwistingSlashMasterySkillPlugIn_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TwistingSlashMasterySkillPlugIn. + /// + public static string TwistingSlashMasterySkillPlugIn_Name { + get { + return ResourceManager.GetString("TwistingSlashMasterySkillPlugIn_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Handles the chat command '/unbanacc <acc>'. Unbans an account from the game.. /// diff --git a/src/GameLogic/Properties/PlugInResources.resx b/src/GameLogic/Properties/PlugInResources.resx index f31d436ba..6cd09989d 100644 --- a/src/GameLogic/Properties/PlugInResources.resx +++ b/src/GameLogic/Properties/PlugInResources.resx @@ -459,6 +459,12 @@ Handles the summon party skill of the dark lord class. + + TwistingSlashMasterySkillPlugIn + + + Handles the twisting slash mastery skill of the dark knight class. Based on a chance, it may push the targets 2 squares away from the attacker. + Add agility chat command diff --git a/src/GameLogic/SkillList.cs b/src/GameLogic/SkillList.cs index 586ebdd09..b8d62c459 100644 --- a/src/GameLogic/SkillList.cs +++ b/src/GameLogic/SkillList.cs @@ -15,6 +15,13 @@ namespace MUnique.OpenMU.GameLogic; /// public sealed class SkillList : ISkillList, IDisposable { + private const ushort DurabilityReduction1SkillId = 300; + private const ushort DurabilityReduction1FistMasterSkillId = 578; + private const short TwistingSlashMasterySkillId = 332; + private const short RagefulBlowMasterySkillId = 333; + + private readonly short[] _castedSkillsWithPassiveBoost = [TwistingSlashMasterySkillId, RagefulBlowMasterySkillId]; + private readonly IDictionary _availableSkills; private readonly ICollection _learnedSkills; @@ -52,7 +59,8 @@ public SkillList(Player player) .Where(item => (item.Definition ?? throw Error.NotInitializedProperty(item, nameof(item.Definition))).Skill != null) .ForEach(item => this.AddItemSkillAsync(item.Definition!.Skill!).AsTask().WaitAndUnwrapException()); this._player.Inventory.EquippedItemsChanged += this.Inventory_WearingItemsChangedAsync; - foreach (var skill in this._learnedSkills.Where(s => s.Skill!.SkillType == SkillType.PassiveBoost)) + foreach (var skill in this._learnedSkills + .Where(s => s.Skill!.SkillType == SkillType.PassiveBoost || this._castedSkillsWithPassiveBoost.Contains(s.Skill.Number))) { this.CreatePowerUpForPassiveSkill(skill); } @@ -153,11 +161,12 @@ private async ValueTask AddLearnedSkillAsync(SkillEntry skill) this._availableSkills.Add(skill.Skill!.Number.ToUnsigned(), skill); this._learnedSkills.Add(skill); - if (skill.Skill.SkillType == SkillType.PassiveBoost) + if (skill.Skill.SkillType == SkillType.PassiveBoost || this._castedSkillsWithPassiveBoost.Contains(skill.Skill.Number)) { this.CreatePowerUpForPassiveSkill(skill); } - else + + if (skill.Skill.SkillType != SkillType.PassiveBoost) { await this._player.InvokeViewPlugInAsync(p => p.AddSkillAsync(skill.Skill)).ConfigureAwait(false); } @@ -185,6 +194,13 @@ private void CreatePowerUpWrappers(SkillEntry skillEntry) var passiveBoost = new PassiveSkillBoostPowerUp(skillEntry); this.PassivePowerUps.Add(passiveBoost); this.PassivePowerUps.Add(new PowerUpWrapper(passiveBoost, masterDefinition.TargetAttribute, this._player.Attributes!)); + + if (skillEntry.Skill.Number == DurabilityReduction1SkillId || skillEntry.Skill.Number == DurabilityReduction1FistMasterSkillId) + { + var durabilityReductionFactorBoost = new PassiveSkillBoostPowerUp(skillEntry, true); + this.PassivePowerUps.Add(durabilityReductionFactorBoost); + this.PassivePowerUps.Add(new PowerUpWrapper(durabilityReductionFactorBoost, Stats.DurabilityReductionFactor, this._player.Attributes!)); + } } private async ValueTask Inventory_WearingItemsChangedAsync(ItemEventArgs eventArgs) @@ -210,12 +226,22 @@ private sealed class PassiveSkillBoostPowerUp : IElement, IDisposable { private readonly SkillEntry _skillEntry; - public PassiveSkillBoostPowerUp(SkillEntry skillEntry) + public PassiveSkillBoostPowerUp(SkillEntry skillEntry, bool isDurabilityReductionFactor = false) { this._skillEntry = skillEntry; - this.Value = this._skillEntry.CalculateValue(); - this.AggregateType = this._skillEntry.Skill!.MasterDefinition!.Aggregation; - this._skillEntry.PropertyChanged += this.OnSkillEntryOnPropertyChanged; + + if (isDurabilityReductionFactor) + { + this.Value = -1 / 500f; + this.AggregateType = AggregateType.AddRaw; + this._skillEntry.PropertyChanged += this.OnDurabilityReductionSkillEntryOnPropertyChanged; + } + else + { + this.Value = this._skillEntry.CalculateValue(); + this.AggregateType = this._skillEntry.Skill!.MasterDefinition!.Aggregation; + this._skillEntry.PropertyChanged += this.OnSkillEntryOnPropertyChanged; + } } public event EventHandler? ValueChanged; @@ -227,6 +253,7 @@ public PassiveSkillBoostPowerUp(SkillEntry skillEntry) public void Dispose() { this._skillEntry.PropertyChanged -= this.OnSkillEntryOnPropertyChanged; + this._skillEntry.PropertyChanged -= this.OnDurabilityReductionSkillEntryOnPropertyChanged; } /// @@ -243,5 +270,14 @@ private void OnSkillEntryOnPropertyChanged(object? sender, PropertyChangedEventA this.ValueChanged?.Invoke(this, EventArgs.Empty); } } + + private void OnDurabilityReductionSkillEntryOnPropertyChanged(object? sender, PropertyChangedEventArgs eventArgs) + { + if (eventArgs.PropertyName == nameof(SkillEntry.Level)) + { + this.Value = -this._skillEntry.Level / 500f; + this.ValueChanged?.Invoke(this, EventArgs.Empty); + } + } } } \ No newline at end of file diff --git a/src/Persistence/Initialization/CharacterClasses/CharacterClassInitialization.cs b/src/Persistence/Initialization/CharacterClasses/CharacterClassInitialization.cs index 59c0f3c99..8568a1fde 100644 --- a/src/Persistence/Initialization/CharacterClasses/CharacterClassInitialization.cs +++ b/src/Persistence/Initialization/CharacterClasses/CharacterClassInitialization.cs @@ -112,11 +112,13 @@ private void AddCommonAttributeRelationships(ICollection attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumPhysBaseDmg, 1, Stats.MaximumPhysBaseDmgByWeapon)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MinimumPhysBaseDmg, 1, Stats.BaseMinDamageBonus)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumPhysBaseDmg, 1, Stats.BaseMaxDamageBonus)); - attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmg, 1, Stats.BaseDamageBonus)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmg, 1, Stats.BaseDamageBonus, aggregateType: AggregateType.AddFinal)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MinimumPhysBaseDmg, 1, Stats.PhysicalBaseDmg)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumPhysBaseDmg, 1, Stats.PhysicalBaseDmg)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MinimumPhysBaseDmg, 1, Stats.PhysicalBaseDmgIncrease, aggregateType: AggregateType.Multiplicate)); attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumPhysBaseDmg, 1, Stats.PhysicalBaseDmgIncrease, aggregateType: AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumHealth, 1, Stats.SwellLifeHealthIncrease, aggregateType: AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumMana, 1, Stats.SwellLifeManaIncrease, aggregateType: AggregateType.Multiplicate)); // If two weapons are equipped (DK, MG, Sum, RF) we subtract the half of the sum of the speeds again from the attack speed attributeRelationships.Add(this.CreateAttributeRelationship(Stats.AreTwoWeaponsEquipped, 1, Stats.EquippedWeaponCount)); @@ -167,6 +169,9 @@ private void AddCommonBaseAttributeValues(ICollection baseA baseAttributeValues.Add(this.CreateConstValueAttribute(-1, Stats.AreTwoWeaponsEquipped)); baseAttributeValues.Add(this.CreateConstValueAttribute(-1, Stats.HasDoubleWield)); baseAttributeValues.Add(this.CreateConstValueAttribute(1, Stats.DefenseDecrement)); + baseAttributeValues.Add(this.CreateConstValueAttribute(1, Stats.SwellLifeHealthIncrease)); + baseAttributeValues.Add(this.CreateConstValueAttribute(1, Stats.SwellLifeManaIncrease)); + baseAttributeValues.Add(this.CreateConstValueAttribute(0.1f, Stats.DurabilityReductionFactor)); if (isMaster) { @@ -187,11 +192,16 @@ private void AddCommonBaseAttributeValues(ICollection baseA private void AddDoubleWieldAttributeRelationships(ICollection attributeRelationships) { attributeRelationships.Add(this.CreateAttributeRelationship(Stats.HasDoubleWield, 1, Stats.DoubleWieldWeaponCount, InputOperator.Maximum)); - var tempDoubleWield = this.Context.CreateNew(Guid.NewGuid(), "Temp Double Wield multiplier", string.Empty); - this.GameConfiguration.Attributes.Add(tempDoubleWield); - attributeRelationships.Add(this.CreateAttributeRelationship(tempDoubleWield, -0.45f, Stats.HasDoubleWield)); - attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmgIncrease, 1, tempDoubleWield, InputOperator.Add, AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmgIncrease, 0.55f, Stats.HasDoubleWield, InputOperator.ExponentiateByAttribute, AggregateType.Multiplicate)); attributeRelationships.Add(this.CreateConditionalRelationship(Stats.MinimumPhysBaseDmgByWeapon, Stats.HasDoubleWield, Stats.MinPhysBaseDmgByRightWeapon)); attributeRelationships.Add(this.CreateConditionalRelationship(Stats.MaximumPhysBaseDmgByWeapon, Stats.HasDoubleWield, Stats.MaxPhysBaseDmgByRightWeapon)); + + // We need to average the base damage of the two weapons and their item option and excellent options. + // For PhysicalBaseDmgIncrease, to avoid using extra attributes, we use ad-hoc AggregateTypes (see ItemPowerUpFactory.GetPowerUpsOfItemOptions()) + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MinimumPhysBaseDmgByWeapon, 0.5f, Stats.HasDoubleWield, InputOperator.ExponentiateByAttribute, AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.MaximumPhysBaseDmgByWeapon, 0.5f, Stats.HasDoubleWield, InputOperator.ExponentiateByAttribute, AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmg, 0.5f, Stats.HasDoubleWield, InputOperator.ExponentiateByAttribute, AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmgIncrease, 0.5f, Stats.HasDoubleWield, InputOperator.ExponentiateByAttribute, AggregateType.Multiplicate)); + attributeRelationships.Add(this.CreateAttributeRelationship(Stats.PhysicalBaseDmgIncrease, 1, Stats.HasDoubleWield)); } } \ No newline at end of file diff --git a/src/Persistence/Initialization/CharacterClasses/ClassDarkKnight.cs b/src/Persistence/Initialization/CharacterClasses/ClassDarkKnight.cs index 29acc6b9d..9a2a72c7e 100644 --- a/src/Persistence/Initialization/CharacterClasses/ClassDarkKnight.cs +++ b/src/Persistence/Initialization/CharacterClasses/ClassDarkKnight.cs @@ -76,6 +76,8 @@ protected CharacterClass CreateDarkKnight(CharacterClassNumber number, string na result.AttributeCombinations.Add(this.CreateAttributeRelationship(Stats.ComboBonus, 0.5f, Stats.TotalEnergy)); result.AttributeCombinations.Add(this.CreateConditionalRelationship(Stats.AttackSpeedAny, Stats.IsOneHandedSwordEquipped, Stats.WeaponMasteryAttackSpeed)); + result.AttributeCombinations.Add(this.CreateConditionalRelationship(Stats.MaceMasteryStunChance, Stats.IsMaceEquipped, Stats.MasteryStunChance)); + result.AttributeCombinations.Add(this.CreateConditionalRelationship(Stats.DoubleDamageChance, Stats.IsSpearEquipped, Stats.SpearMasteryDoubleDamageChance)); result.AttributeCombinations.Add(this.CreateAttributeRelationship(Stats.FenrirBaseDmg, 1.0f / 3, Stats.TotalStrength)); result.AttributeCombinations.Add(this.CreateAttributeRelationship(Stats.FenrirBaseDmg, 1.0f / 5, Stats.TotalAgility)); diff --git a/src/Persistence/Initialization/Skills/LifeSwellEffectInitializer.cs b/src/Persistence/Initialization/Skills/LifeSwellEffectInitializer.cs index acbe6b3ec..ce4fa9e0b 100644 --- a/src/Persistence/Initialization/Skills/LifeSwellEffectInitializer.cs +++ b/src/Persistence/Initialization/Skills/LifeSwellEffectInitializer.cs @@ -32,37 +32,46 @@ public override void Initialize() magicEffect.Number = (byte)MagicEffectNumber.GreaterFortitude; magicEffect.Name = "Life Swell Skill Effect"; magicEffect.InformObservers = true; + magicEffect.SubType = 4; magicEffect.SendDuration = false; magicEffect.StopByDeath = true; magicEffect.Duration = this.Context.CreateNew(); magicEffect.Duration.ConstantValue.Value = 60f; + magicEffect.Duration.MaximumValue = 180f; var durationPerEnergy = this.Context.CreateNew(); durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(this.GameConfiguration); durationPerEnergy.InputOperator = InputOperator.Multiply; - durationPerEnergy.InputOperand = 1f / 5f; // 5 energy adds 1 second duration + durationPerEnergy.InputOperand = 1f / 180f; // 180 energy adds 1 second duration magicEffect.Duration.RelatedValues.Add(durationPerEnergy); - var powerUpDefinition = this.Context.CreateNew(); - magicEffect.PowerUpDefinitions.Add(powerUpDefinition); - powerUpDefinition.TargetAttribute = Stats.MaximumHealth.GetPersistent(this.GameConfiguration); + var healthPowerUpDefinition = this.Context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(healthPowerUpDefinition); + healthPowerUpDefinition.TargetAttribute = Stats.SwellLifeHealthIncrease.GetPersistent(this.GameConfiguration); // one percent per 20 energy var boostPerEnergy = this.Context.CreateNew(); boostPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(this.GameConfiguration); - boostPerEnergy.InputOperator = InputOperator.ExponentiateByAttribute; - boostPerEnergy.InputOperand = 1f + (0.01f / 20f); + boostPerEnergy.InputOperator = InputOperator.Multiply; + boostPerEnergy.InputOperand = 1f / 2000; // one percent per 100 vitality var boostPerVitality = this.Context.CreateNew(); boostPerVitality.InputAttribute = Stats.TotalVitality.GetPersistent(this.GameConfiguration); - boostPerVitality.InputOperator = InputOperator.ExponentiateByAttribute; - boostPerVitality.InputOperand = 1f + (0.01f / 100f); + boostPerVitality.InputOperator = InputOperator.Multiply; + boostPerVitality.InputOperand = 1f / 10000; - powerUpDefinition.Boost = this.Context.CreateNew(); - powerUpDefinition.Boost.ConstantValue.Value = 1.12f; - powerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate; - powerUpDefinition.Boost.RelatedValues.Add(boostPerEnergy); - powerUpDefinition.Boost.RelatedValues.Add(boostPerVitality); + // one percent per party member in view + var boostPerPartyMember = this.Context.CreateNew(); + boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(this.GameConfiguration); + boostPerPartyMember.InputOperator = InputOperator.Multiply; + boostPerPartyMember.InputOperand = 1f / 100; + + healthPowerUpDefinition.Boost = this.Context.CreateNew(); + healthPowerUpDefinition.Boost.ConstantValue.Value = 0.12f; + healthPowerUpDefinition.Boost.MaximumValue = 2f; + healthPowerUpDefinition.Boost.RelatedValues.Add(boostPerEnergy); + healthPowerUpDefinition.Boost.RelatedValues.Add(boostPerVitality); + healthPowerUpDefinition.Boost.RelatedValues.Add(boostPerPartyMember); } } \ No newline at end of file diff --git a/src/Persistence/Initialization/Skills/LifeSwellProficiencyEffectInitializer.cs b/src/Persistence/Initialization/Skills/LifeSwellProficiencyEffectInitializer.cs new file mode 100644 index 000000000..18563afbd --- /dev/null +++ b/src/Persistence/Initialization/Skills/LifeSwellProficiencyEffectInitializer.cs @@ -0,0 +1,83 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Skills; + +using MUnique.OpenMU.AttributeSystem; +using MUnique.OpenMU.DataModel.Attributes; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic.Attributes; + +/// +/// Initializer which initializes the life swell proficiency effect. +/// +public class LifeSwellProficiencyEffectInitializer : InitializerBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The game configuration. + public LifeSwellProficiencyEffectInitializer(IContext context, GameConfiguration gameConfiguration) + : base(context, gameConfiguration) + { + } + + /// + public override void Initialize() + { + var magicEffect = this.Context.CreateNew(); + this.GameConfiguration.MagicEffects.Add(magicEffect); + magicEffect.Number = (byte)MagicEffectNumber.GreaterFortitudeProficiency; + magicEffect.Name = "Life Swell Proficiency Skill Effect"; + + var lifeSwellEffect = this.GameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.GreaterFortitude); + magicEffect.InformObservers = lifeSwellEffect.InformObservers; + magicEffect.SubType = lifeSwellEffect.SubType; + magicEffect.SendDuration = lifeSwellEffect.SendDuration; + magicEffect.StopByDeath = lifeSwellEffect.StopByDeath; + magicEffect.Duration = this.Context.CreateNew(); + magicEffect.Duration.ConstantValue.Value = lifeSwellEffect.Duration!.ConstantValue.Value; + magicEffect.Duration.MaximumValue = lifeSwellEffect.Duration.MaximumValue; + + foreach (var durationRelatedValue in lifeSwellEffect.Duration.RelatedValues) + { + var durationRelatedValueCopy = this.Context.CreateNew(); + durationRelatedValueCopy.InputAttribute = durationRelatedValue.InputAttribute!.GetPersistent(this.GameConfiguration); + durationRelatedValueCopy.InputOperator = durationRelatedValue.InputOperator; + durationRelatedValueCopy.InputOperand = durationRelatedValue.InputOperand; + magicEffect.Duration.RelatedValues.Add(durationRelatedValueCopy); + } + + foreach (var powerUp in lifeSwellEffect.PowerUpDefinitions) + { + var powerUpCopy = this.Context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(powerUpCopy); + powerUpCopy.TargetAttribute = powerUp.TargetAttribute!.GetPersistent(this.GameConfiguration); + powerUpCopy.Boost = this.Context.CreateNew(); + powerUpCopy.Boost.ConstantValue.Value = powerUp.Boost!.ConstantValue.Value; + + foreach (var boostRelatedValue in powerUp.Boost.RelatedValues) + { + var boostRelatedValueCopy = this.Context.CreateNew(); + boostRelatedValueCopy.InputAttribute = boostRelatedValue.InputAttribute!.GetPersistent(this.GameConfiguration); + boostRelatedValueCopy.InputOperator = boostRelatedValue.InputOperator; + boostRelatedValueCopy.InputOperand = boostRelatedValue.InputOperand; + powerUpCopy.Boost.RelatedValues.Add(boostRelatedValueCopy); + } + } + + // one percent per party member in view + var boostPerPartyMember = this.Context.CreateNew(); + boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(this.GameConfiguration); + boostPerPartyMember.InputOperator = InputOperator.Multiply; + boostPerPartyMember.InputOperand = 1f / 100; + + var manaPowerUpDefinition = this.Context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(manaPowerUpDefinition); + manaPowerUpDefinition.TargetAttribute = Stats.SwellLifeManaIncrease.GetPersistent(this.GameConfiguration); + manaPowerUpDefinition.Boost = this.Context.CreateNew(); + manaPowerUpDefinition.Boost.RelatedValues.Add(boostPerPartyMember); + } +} \ No newline at end of file diff --git a/src/Persistence/Initialization/Skills/MagicEffectNumber.cs b/src/Persistence/Initialization/Skills/MagicEffectNumber.cs index a38d8cb9f..5a4b831cf 100644 --- a/src/Persistence/Initialization/Skills/MagicEffectNumber.cs +++ b/src/Persistence/Initialization/Skills/MagicEffectNumber.cs @@ -296,6 +296,11 @@ internal enum MagicEffectNumber : short /// WizEnhance = 0x52, + /// + /// The cold effect caused by chain drive and strike of destruction skills. Like , it also slows down movement. + /// + Cold = 0x56, + /// /// The ignore defense effect of the rage fighter. /// @@ -316,6 +321,11 @@ internal enum MagicEffectNumber : short /// DecreaseBlock = 132, + /// + /// The greater fortitude (swell life proficiency) effect. + /// + GreaterFortitudeProficiency = 135, + /// /// The wiz enhance strengthener effect. /// diff --git a/src/Persistence/Initialization/Skills/SkillsInitializerBase.cs b/src/Persistence/Initialization/Skills/SkillsInitializerBase.cs index 7443c6427..4c7cffe5d 100644 --- a/src/Persistence/Initialization/Skills/SkillsInitializerBase.cs +++ b/src/Persistence/Initialization/Skills/SkillsInitializerBase.cs @@ -184,19 +184,28 @@ private void ApplyElementalModifier(ElementalType elementalModifier, Skill skill return; } + if ((SkillNumber)skill.Number is SkillNumber.ChainDrive) + { + skill.ElementalModifierTarget = Stats.IceResistance.GetPersistent(this.GameConfiguration); + skill.SkipElementalModifier = true; + skill.MagicEffectDef = this.CreateEffect(ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10, 0.4f); + return; + } + + if ((SkillNumber)skill.Number is SkillNumber.StrikeofDestruction) + { + skill.ElementalModifierTarget = Stats.IceResistance.GetPersistent(this.GameConfiguration); + skill.SkipElementalModifier = true; + skill.MagicEffectDef = this.CreateEffect(ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10); + return; + } + switch (elementalModifier) { case ElementalType.Ice: skill.ElementalModifierTarget = Stats.IceResistance.GetPersistent(this.GameConfiguration); skill.MagicEffectDef = this.CreateEffect(ElementalType.Ice, MagicEffectNumber.Iced, Stats.IsIced, 10); - if ((SkillNumber)skill.Number is SkillNumber.ChainDrive) - { - skill.SkipElementalModifier = true; - skill.MagicEffectDef.Chance = this.Context.CreateNew(); - skill.MagicEffectDef.Chance.ConstantValue.Value = 0.4f; - } - break; case ElementalType.Poison: skill.ElementalModifierTarget = Stats.PoisonResistance.GetPersistent(this.GameConfiguration); @@ -242,12 +251,13 @@ private void ApplyElementalModifier(ElementalType elementalModifier, Skill skill } } - private MagicEffectDefinition CreateEffect(ElementalType type, MagicEffectNumber effectNumber, AttributeDefinition targetAttribute, float durationInSeconds) + private MagicEffectDefinition CreateEffect(ElementalType type, MagicEffectNumber effectNumber, AttributeDefinition targetAttribute, float durationInSeconds, float chance = 0) { if (this.GameConfiguration.MagicEffects.FirstOrDefault( e => e.Number == (short)effectNumber && e.SubType == (byte)(0xFF - type) && Equals(e.Duration?.ConstantValue.Value, durationInSeconds) + && Equals(e.Chance?.ConstantValue.Value, chance) && e.PowerUpDefinitions.FirstOrDefault()?.TargetAttribute == targetAttribute) is { } existingEffect) { return existingEffect; @@ -267,6 +277,13 @@ private MagicEffectDefinition CreateEffect(ElementalType type, MagicEffectNumber powerUpDefinition.Boost = this.Context.CreateNew(); powerUpDefinition.Boost.ConstantValue.Value = 1; powerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(this.GameConfiguration); + + if (chance > 0) + { + effect.Chance = this.Context.CreateNew(); + effect.Chance.ConstantValue.Value = chance; + } + return effect; } diff --git a/src/Persistence/Initialization/Skills/StunEffectInitializer.cs b/src/Persistence/Initialization/Skills/StunEffectInitializer.cs index d27f58abb..f7ff60542 100644 --- a/src/Persistence/Initialization/Skills/StunEffectInitializer.cs +++ b/src/Persistence/Initialization/Skills/StunEffectInitializer.cs @@ -45,7 +45,7 @@ public override void Initialize() // Placeholder for master skills that use this effect var stunChancePowerUpDefinition = this.Context.CreateNew(); magicEffect.PowerUpDefinitions.Add(stunChancePowerUpDefinition); - stunChancePowerUpDefinition.TargetAttribute = Stats.StunChance.GetPersistent(this.GameConfiguration); + stunChancePowerUpDefinition.TargetAttribute = Stats.MasteryStunChance.GetPersistent(this.GameConfiguration); stunChancePowerUpDefinition.Boost = this.Context.CreateNew(); stunChancePowerUpDefinition.Boost.ConstantValue.Value = 0; } diff --git a/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn075.cs b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn075.cs new file mode 100644 index 000000000..d89e28087 --- /dev/null +++ b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn075.cs @@ -0,0 +1,30 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.PlugIns; + +/// +/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("B8F3E2C1-4D5A-6F78-9B0C-2E7D1A3F5B6C")] +public class FinishDarkKnightMasterTreePlugIn075 : FinishDarkKnightMasterTreePlugInBase +{ + /// + public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTree075; + + /// + public override string DataInitializationKey => Version075.DataInitialization.Id; + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false); + } +} diff --git a/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn095d.cs b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn095d.cs new file mode 100644 index 000000000..8ca357894 --- /dev/null +++ b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugIn095d.cs @@ -0,0 +1,24 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.PlugIns; + +/// +/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("D4F7A9C2-1B3E-56D8-9F0A-7C2E4B1D5A8F")] +public class FinishDarkKnightMasterTreePlugIn095D : FinishDarkKnightMasterTreePlugInBase +{ + /// + public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTree095d; + + /// + public override string DataInitializationKey => Version095d.DataInitialization.Id; +} diff --git a/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInBase.cs b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInBase.cs new file mode 100644 index 000000000..1b4c253c7 --- /dev/null +++ b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInBase.cs @@ -0,0 +1,192 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using MUnique.OpenMU.AttributeSystem; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic; +using MUnique.OpenMU.GameLogic.Attributes; + +/// +/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations. +/// +public abstract class FinishDarkKnightMasterTreePlugInBase : UpdatePlugInBase +{ + /// + /// The plug in name. + /// + internal const string PlugInName = "Fix Double Wield Damage Calculations"; + + /// + /// The plug in description. + /// + internal const string PlugInDescription = "This update fixes the double wield damage calculations."; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override bool IsMandatory => true; + + /// + public override DateTime CreatedAt => new(2026, 5, 8, 16, 0, 0, DateTimeKind.Utc); + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + // Create new Stats + var maceMasteryStunChance = context.CreateNew(Stats.MaceMasteryStunChance.Id, Stats.MaceMasteryStunChance.Designation, Stats.MaceMasteryStunChance.Description); + gameConfiguration.Attributes.Add(maceMasteryStunChance); + var ragefulBlowMasteryDurabilityDecChance = context.CreateNew(Stats.RagefulBlowMasteryDurabilityDecChance.Id, Stats.RagefulBlowMasteryDurabilityDecChance.Designation, Stats.RagefulBlowMasteryDurabilityDecChance.Description); + gameConfiguration.Attributes.Add(ragefulBlowMasteryDurabilityDecChance); + var spearMasteryDoubleDamageChance = context.CreateNew(Stats.SpearMasteryDoubleDamageChance.Id, Stats.SpearMasteryDoubleDamageChance.Designation, Stats.SpearMasteryDoubleDamageChance.Description); + gameConfiguration.Attributes.Add(spearMasteryDoubleDamageChance); + var swellLifeHealthIncrease = context.CreateNew(Stats.SwellLifeHealthIncrease.Id, Stats.SwellLifeHealthIncrease.Designation, Stats.SwellLifeHealthIncrease.Description); + gameConfiguration.Attributes.Add(swellLifeHealthIncrease); + var swellLifeManaIncrease = context.CreateNew(Stats.SwellLifeManaIncrease.Id, Stats.SwellLifeManaIncrease.Designation, Stats.SwellLifeManaIncrease.Description); + gameConfiguration.Attributes.Add(swellLifeManaIncrease); + + // Update attribute combinations + var maximumHealth = Stats.MaximumHealth.GetPersistent(gameConfiguration); + var maximumMana = Stats.MaximumMana.GetPersistent(gameConfiguration); + var obsoleteTempDoubleWieldMultipliers = gameConfiguration.Attributes.Where(a => a.Designation == "Temp Double Wield multiplier"); // we remove this later + var hasDoubleWield = Stats.HasDoubleWield.GetPersistent(gameConfiguration); + var minimumPhysBaseDmgByWeapon = Stats.MinimumPhysBaseDmgByWeapon.GetPersistent(gameConfiguration); + var maximumPhysBaseDmgByWeapon = Stats.MaximumPhysBaseDmgByWeapon.GetPersistent(gameConfiguration); + var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration); + var physicalBaseDmgIncrease = Stats.PhysicalBaseDmgIncrease.GetPersistent(gameConfiguration); + var doubleDamageChance = Stats.DoubleDamageChance.GetPersistent(gameConfiguration); + var isSpearEquipped = Stats.IsSpearEquipped.GetPersistent(gameConfiguration); + + gameConfiguration.CharacterClasses.ForEach(charClass => + { + // Update common combination attribute + if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.PhysicalBaseDmg && attrCombo.InputAttribute == Stats.BaseDamageBonus) is { } baseDmgBonusToPhysicalBaseDmg) + { + baseDmgBonusToPhysicalBaseDmg.AggregateType = AggregateType.AddFinal; + } + + // Add new attribute combinations + var swellLifeHealthIncreaseToMaxHealth = context.CreateNew( + maximumHealth, + 1, + swellLifeHealthIncrease, + InputOperator.Multiply, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + var swellLifeManaIncreaseToMaxMana = context.CreateNew( + maximumMana, + 1, + swellLifeManaIncrease, + InputOperator.Multiply, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + charClass.AttributeCombinations.Add(swellLifeHealthIncreaseToMaxHealth); + charClass.AttributeCombinations.Add(swellLifeManaIncreaseToMaxMana); + charClass.BaseAttributeValues.Add(context.CreateNew(1, swellLifeHealthIncrease)); + charClass.BaseAttributeValues.Add(context.CreateNew(1, swellLifeManaIncrease)); + + // Update/add double wield attribute combinations + if (charClass.Number == 4 || charClass.Number == 6 || charClass.Number == 7 // DK classes + || charClass.Number == 12 || charClass.Number == 13 // MG classes + || charClass.Number == 24 || charClass.Number == 25) // RF classes + { + if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.InputOperand == -0.45f) is { } hasDoubleWieldToTempDoubleWieldMultiplier) + { + charClass.AttributeCombinations.Remove(hasDoubleWieldToTempDoubleWieldMultiplier); + } + + if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.PhysicalBaseDmgIncrease + && obsoleteTempDoubleWieldMultipliers.Contains(attrCombo.InputAttribute)) is { } tempDoubleWieldToPhysicalBaseDmgIncrease) + { + tempDoubleWieldToPhysicalBaseDmgIncrease.InputAttribute = hasDoubleWield; + tempDoubleWieldToPhysicalBaseDmgIncrease.InputOperand = 0.55f; + tempDoubleWieldToPhysicalBaseDmgIncrease.InputOperator = InputOperator.ExponentiateByAttribute; + } + + var hasDoubleWieldToMinimumPhysBaseDmgByWeapon = context.CreateNew( + minimumPhysBaseDmgByWeapon, + 0.5f, + hasDoubleWield, + InputOperator.ExponentiateByAttribute, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + var hasDoubleWieldToMaximumPhysBaseDmgByWeapon = context.CreateNew( + maximumPhysBaseDmgByWeapon, + 0.5f, + hasDoubleWield, + InputOperator.ExponentiateByAttribute, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + var hasDoubleWieldToPhysicalBaseDmg = context.CreateNew( + physicalBaseDmg, + 0.5f, + hasDoubleWield, + InputOperator.ExponentiateByAttribute, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + var hasDoubleWieldToPhysicalBaseDmgIncrease = context.CreateNew( + physicalBaseDmgIncrease, + 0.5f, + hasDoubleWield, + InputOperator.ExponentiateByAttribute, + default(AttributeDefinition?), + AggregateType.Multiplicate); + + var hasDoubleWieldToPhysicalBaseDmgIncreaseRaw = context.CreateNew( + physicalBaseDmgIncrease, + 1, + hasDoubleWield, + InputOperator.Multiply, + default(AttributeDefinition?), + AggregateType.AddRaw); + + charClass.AttributeCombinations.Add(hasDoubleWieldToMinimumPhysBaseDmgByWeapon); + charClass.AttributeCombinations.Add(hasDoubleWieldToMaximumPhysBaseDmgByWeapon); + charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmg); + charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmgIncrease); + charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmgIncreaseRaw); + + if (charClass.Number == 4 || charClass.Number == 6 || charClass.Number == 7) + { + var spearMasteryDoubleDamageChanceToDoubleDamageChance = context.CreateNew( + doubleDamageChance, + isSpearEquipped, + spearMasteryDoubleDamageChance, + AggregateType.AddRaw); + + charClass.AttributeCombinations.Add(spearMasteryDoubleDamageChanceToDoubleDamageChance); + } + } + }); + + // Removed obsolete attributes + foreach (var obsoleteTempDoubleWield in obsoleteTempDoubleWieldMultipliers.ToList()) + { + gameConfiguration.Attributes.Remove(obsoleteTempDoubleWield); + } + + // Update wings physical base dmg option + var wingsSlot = gameConfiguration.ItemSlotTypes.First(st => st.ItemSlots.Contains(InventoryConstants.WingsSlot)); + foreach (var wing in gameConfiguration.Items.Where(i => i.ItemSlot == wingsSlot)) + { + if (wing.PossibleItemOptions.SelectMany(pio => pio.PossibleOptions).FirstOrDefault(po => po.PowerUpDefinition?.TargetAttribute == physicalBaseDmg) is { } pio) + { + foreach (var levelOption in pio.LevelDependentOptions) + { + levelOption.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal; + } + } + } + } +} diff --git a/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInSeason6.cs b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInSeason6.cs new file mode 100644 index 000000000..0a941032e --- /dev/null +++ b/src/Persistence/Initialization/Updates/FinishDarkKnightMasterTreePlugInSeason6.cs @@ -0,0 +1,289 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.AttributeSystem; +using MUnique.OpenMU.DataModel.Attributes; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.GameLogic.Attributes; +using MUnique.OpenMU.Persistence.Initialization.Skills; +using MUnique.OpenMU.PlugIns; + +/// +/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("F7B2C9E4-1A3D-56F8-9B0C-4E2D7A1F8B3C")] +public class FinishDarkKnightMasterTreePlugInSeason6 : FinishDarkKnightMasterTreePlugInBase +{ + /// + /// The plug in name. + /// + internal new const string PlugInName = "Finish Dark Knight Master Tree PlugIn"; + + /// + /// The plug in description. + /// + internal new const string PlugInDescription = "This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations."; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTreeSeason6; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false); + + var masteryStunChance = Stats.MasteryStunChance.GetPersistent(gameConfiguration); + var ragefulBlowMasteryDurabilityDecChance = Stats.RagefulBlowMasteryDurabilityDecChance.GetPersistent(gameConfiguration); + var spearMasteryDoubleDamageChance = Stats.SpearMasteryDoubleDamageChance.GetPersistent(gameConfiguration); + var swellLifeHealthIncrease = Stats.SwellLifeHealthIncrease.GetPersistent(gameConfiguration); + var swellLifeManaIncrease = Stats.SwellLifeManaIncrease.GetPersistent(gameConfiguration); + var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration); + + // Update Life Swell effect + var lifeSwellEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.GreaterFortitude); + lifeSwellEffect.SubType = 4; + lifeSwellEffect.Duration!.MaximumValue = 180; + + if (lifeSwellEffect.Duration.RelatedValues.FirstOrDefault() is { } durationPerEnergy) + { + durationPerEnergy.InputOperand = 1f / 180f; + } + + if (lifeSwellEffect.PowerUpDefinitions.FirstOrDefault() is { } maxHealth) + { + maxHealth.TargetAttribute = swellLifeHealthIncrease; + maxHealth.Boost!.ConstantValue.Value = 0.12f; + maxHealth.Boost.ConstantValue.AggregateType = AggregateType.AddRaw; + maxHealth.Boost.MaximumValue = 2f; + + foreach (var boostRelatedValue in maxHealth.Boost.RelatedValues) + { + if (boostRelatedValue.InputAttribute == Stats.TotalEnergy) + { + boostRelatedValue.InputOperator = InputOperator.Multiply; + boostRelatedValue.InputOperand = 1f / 2000; + continue; + } + + if (boostRelatedValue.InputAttribute == Stats.TotalVitality) + { + boostRelatedValue.InputOperator = InputOperator.Multiply; + boostRelatedValue.InputOperand = 1f / 10000; + } + } + + var boostPerPartyMember = context.CreateNew(); + boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(gameConfiguration); + boostPerPartyMember.InputOperator = InputOperator.Multiply; + boostPerPartyMember.InputOperand = 1f / 100; + maxHealth.Boost.RelatedValues.Add(boostPerPartyMember); + } + + // Create Life Swell Proficiency Skill Effect + var lifeSwellProficiencyEffect = this.CreateLifeSwellProficiencyEffect(context, gameConfiguration); + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeProficiency) is { } skill) + { + skill.MagicEffectDef = lifeSwellProficiencyEffect; + } + + // Restore iced effect (revert bug) + if (gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.Iced && e.Chance is { }) is { } originalIced) + { + originalIced.Chance = null; + } + + // Create chain drive cold effect + var chainDriveCold = this.CreateEffect(context, gameConfiguration, ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10, 0.4f); + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDrive) is { } chainDrive) + { + chainDrive.MagicEffectDef = chainDriveCold; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDriveStrengthener) is { } chainDriveStrengthener) + { + chainDriveStrengthener.MagicEffectDef = chainDriveCold; + } + + // Create strike of destruction cold effect + var strikeOfDestructCold = this.CreateEffect(context, gameConfiguration, ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10); + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.StrikeofDestruction) is { } strikeOfDestruction) + { + strikeOfDestruction.SkipElementalModifier = true; + strikeOfDestruction.MagicEffectDef = strikeOfDestructCold; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.StrikeofDestrStr) is { } strikeOfDestrStr) + { + strikeOfDestrStr.SkipElementalModifier = true; + strikeOfDestrStr.MagicEffectDef = strikeOfDestructCold; + } + + // Update harmony option + if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.Name == "Harmony Physical Attack Options") is { } harmonyPhysAttackOptions + && harmonyPhysAttackOptions.PossibleOptions.FirstOrDefault(o => o.Number == 5) is { } physBaseDmgOpt) + { + foreach (var level in physBaseDmgOpt.LevelDependentOptions) + { + level.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal; + } + } + + // Update gold fenrir option + var goldFenrirOptionId = new Guid("00000083-0081-0000-0000-000000000000"); + if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == goldFenrirOptionId) is { } goldFenrirOption) + { + foreach (var option in goldFenrirOption.PossibleOptions) + { + if (option.PowerUpDefinition?.TargetAttribute == physicalBaseDmg) + { + option.PowerUpDefinition.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal; + } + } + } + + // Update master skills + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TwistingSlashMastery)?.MasterDefinition is { } twistingSlashMastery) + { + twistingSlashMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.TwistingSlashStreng); + twistingSlashMastery.TargetAttribute = Stats.MasteryMoveTargetChance.GetPersistent(gameConfiguration); + twistingSlashMastery.Aggregation = AggregateType.AddRaw; + twistingSlashMastery.ValueFormula = $"{twistingSlashMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.RagefulBlowMastery)?.MasterDefinition is { } ragefulBlowMastery) + { + ragefulBlowMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.RagefulBlowStreng); + ragefulBlowMastery.TargetAttribute = ragefulBlowMasteryDurabilityDecChance; + ragefulBlowMastery.Aggregation = AggregateType.AddRaw; + ragefulBlowMastery.ValueFormula = $"{ragefulBlowMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MaceMastery)?.MasterDefinition is { } maceMastery) + { + maceMastery.TargetAttribute = masteryStunChance; + maceMastery.Aggregation = AggregateType.AddRaw; + maceMastery.ValueFormula = $"{maceMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SpearMastery)?.MasterDefinition is { } spearMastery) + { + spearMastery.TargetAttribute = spearMasteryDoubleDamageChance; + spearMastery.Aggregation = AggregateType.AddRaw; + spearMastery.ValueFormula = $"{spearMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeStrengt)?.MasterDefinition is { } swellLifeStrengt) + { + swellLifeStrengt.TargetAttribute = swellLifeHealthIncrease; + swellLifeStrengt.Aggregation = AggregateType.AddRaw; + swellLifeStrengt.ValueFormula = $"{swellLifeStrengt.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeProficiency)?.MasterDefinition is { } swellLifeProficiency) + { + swellLifeProficiency.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.SwellLifeStrengt); + swellLifeProficiency.TargetAttribute = swellLifeManaIncrease; + swellLifeProficiency.Aggregation = AggregateType.AddRaw; + swellLifeProficiency.ValueFormula = $"{swellLifeProficiency.ValueFormula} / 100"; + } + } + + private MagicEffectDefinition CreateLifeSwellProficiencyEffect(IContext context, GameConfiguration gameConfiguration) + { + var magicEffect = context.CreateNew(); + gameConfiguration.MagicEffects.Add(magicEffect); + magicEffect.Number = (byte)MagicEffectNumber.GreaterFortitudeProficiency; + magicEffect.Name = "Life Swell Proficiency Skill Effect"; + + var lifeSwellEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.GreaterFortitude); + magicEffect.InformObservers = lifeSwellEffect.InformObservers; + magicEffect.SubType = lifeSwellEffect.SubType; + magicEffect.SendDuration = lifeSwellEffect.SendDuration; + magicEffect.StopByDeath = lifeSwellEffect.StopByDeath; + magicEffect.Duration = context.CreateNew(); + magicEffect.Duration.ConstantValue.Value = lifeSwellEffect.Duration!.ConstantValue.Value; + magicEffect.Duration.MaximumValue = lifeSwellEffect.Duration.MaximumValue; + + foreach (var durationRelatedValue in lifeSwellEffect.Duration.RelatedValues) + { + var durationRelatedValueCopy = context.CreateNew(); + durationRelatedValueCopy.InputAttribute = durationRelatedValue.InputAttribute!.GetPersistent(gameConfiguration); + durationRelatedValueCopy.InputOperator = durationRelatedValue.InputOperator; + durationRelatedValueCopy.InputOperand = durationRelatedValue.InputOperand; + magicEffect.Duration.RelatedValues.Add(durationRelatedValueCopy); + } + + foreach (var powerUp in lifeSwellEffect.PowerUpDefinitions) + { + var powerUpCopy = context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(powerUpCopy); + powerUpCopy.TargetAttribute = powerUp.TargetAttribute!.GetPersistent(gameConfiguration); + powerUpCopy.Boost = context.CreateNew(); + powerUpCopy.Boost.ConstantValue.Value = powerUp.Boost!.ConstantValue.Value; + + foreach (var boostRelatedValue in powerUp.Boost.RelatedValues) + { + var boostRelatedValueCopy = context.CreateNew(); + boostRelatedValueCopy.InputAttribute = boostRelatedValue.InputAttribute!.GetPersistent(gameConfiguration); + boostRelatedValueCopy.InputOperator = boostRelatedValue.InputOperator; + boostRelatedValueCopy.InputOperand = boostRelatedValue.InputOperand; + powerUpCopy.Boost.RelatedValues.Add(boostRelatedValueCopy); + } + } + + // one percent per party member in view + var boostPerPartyMember = context.CreateNew(); + boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(gameConfiguration); + boostPerPartyMember.InputOperator = InputOperator.Multiply; + boostPerPartyMember.InputOperand = 1f / 100; + + var manaPowerUpDefinition = context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(manaPowerUpDefinition); + manaPowerUpDefinition.TargetAttribute = Stats.SwellLifeManaIncrease.GetPersistent(gameConfiguration); + manaPowerUpDefinition.Boost = context.CreateNew(); + manaPowerUpDefinition.Boost.RelatedValues.Add(boostPerPartyMember); + + return magicEffect; + } + + private MagicEffectDefinition CreateEffect(IContext context, GameConfiguration gameConfiguration, ElementalType type, MagicEffectNumber effectNumber, AttributeDefinition targetAttribute, float durationInSeconds, float chance = 0) + { + var effect = context.CreateNew(); + gameConfiguration.MagicEffects.Add(effect); + effect.Name = Enum.GetName(effectNumber) ?? string.Empty; + effect.InformObservers = true; + effect.Number = (short)effectNumber; + effect.StopByDeath = true; + effect.SubType = (byte)(0xFF - type); + effect.Duration = context.CreateNew(); + effect.Duration.ConstantValue.Value = durationInSeconds; + var powerUpDefinition = context.CreateNew(); + effect.PowerUpDefinitions.Add(powerUpDefinition); + powerUpDefinition.Boost = context.CreateNew(); + powerUpDefinition.Boost.ConstantValue.Value = 1; + powerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration); + + if (chance > 0) + { + effect.Chance = context.CreateNew(); + effect.Chance.ConstantValue.Value = chance; + } + + return effect; + } +} diff --git a/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs b/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs index e354fd544..7cbb57cec 100644 --- a/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs +++ b/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs @@ -61,7 +61,7 @@ protected override async ValueTask ApplyAsync(IContext context, GameConfiguratio var stunChancePowerUpDefinition = context.CreateNew(); stunnedEffect.PowerUpDefinitions.Add(stunChancePowerUpDefinition); - stunChancePowerUpDefinition.TargetAttribute = Stats.StunChance.GetPersistent(gameConfiguration); + stunChancePowerUpDefinition.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration); stunChancePowerUpDefinition.Boost = context.CreateNew(); stunChancePowerUpDefinition.Boost.ConstantValue.Value = 0; @@ -90,7 +90,7 @@ protected override async ValueTask ApplyAsync(IContext context, GameConfiguratio if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.FireBurstMastery)?.MasterDefinition is { } fireBurstMastery) { fireBurstMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.FireBurstStreng); - fireBurstMastery.TargetAttribute = Stats.StunChance.GetPersistent(gameConfiguration); + fireBurstMastery.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration); fireBurstMastery.Aggregation = AggregateType.AddRaw; fireBurstMastery.ValueFormula = $"{fireBurstMastery.ValueFormula} / 100"; } @@ -104,7 +104,7 @@ protected override async ValueTask ApplyAsync(IContext context, GameConfiguratio if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EarthshakeMastery)?.MasterDefinition is { } earthshakeMastery) { earthshakeMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.EarthshakeStreng); - earthshakeMastery.TargetAttribute = Stats.StunChance.GetPersistent(gameConfiguration); + earthshakeMastery.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration); earthshakeMastery.Aggregation = AggregateType.AddRaw; earthshakeMastery.ValueFormula = $"{earthshakeMastery.ValueFormula} / 100"; } diff --git a/src/Persistence/Initialization/Updates/FixSummonerCurseSkillsPlugIn.cs b/src/Persistence/Initialization/Updates/FixSummonerCurseSkillsPlugIn.cs index e02670e24..ed387b254 100644 --- a/src/Persistence/Initialization/Updates/FixSummonerCurseSkillsPlugIn.cs +++ b/src/Persistence/Initialization/Updates/FixSummonerCurseSkillsPlugIn.cs @@ -57,10 +57,10 @@ protected override async ValueTask ApplyAsync(IContext context, GameConfiguratio var bleedingDamageMultiplier = Stats.BleedingDamageMultiplier.GetPersistent(gameConfiguration); this.AddStatIfNotExists(context, gameConfiguration, Stats.IsBleeding); var isBleeding = Stats.IsBleeding.GetPersistent(gameConfiguration); - this.AddStatIfNotExists(context, gameConfiguration, Stats.StunChance); - var stunChance = Stats.StunChance.GetPersistent(gameConfiguration); - this.AddStatIfNotExists(context, gameConfiguration, Stats.PollutionMoveTargetChance); - var pollutionMoveTargetChance = Stats.PollutionMoveTargetChance.GetPersistent(gameConfiguration); + this.AddStatIfNotExists(context, gameConfiguration, Stats.MasteryStunChance); + var stunChance = Stats.MasteryStunChance.GetPersistent(gameConfiguration); + this.AddStatIfNotExists(context, gameConfiguration, Stats.MasteryMoveTargetChance); + var masteryMoveTargetChance = Stats.MasteryMoveTargetChance.GetPersistent(gameConfiguration); // Add new base attribute to summoner classes var summonerClassNumbers = new[] { (int)CharacterClassNumber.Summoner, (int)CharacterClassNumber.BloodySummoner, (int)CharacterClassNumber.DimensionMaster }; @@ -122,7 +122,7 @@ protected override async ValueTask ApplyAsync(IContext context, GameConfiguratio if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningTomeMastery)?.MasterDefinition is { } lightningTomeMastery) { - lightningTomeMastery.TargetAttribute = pollutionMoveTargetChance; + lightningTomeMastery.TargetAttribute = masteryMoveTargetChance; lightningTomeMastery.Aggregation = AggregateType.AddRaw; lightningTomeMastery.ValueFormula = $"{lightningTomeMastery.ValueFormula} / 100"; } diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs index 88de6ca39..e0eb97929 100644 --- a/src/Persistence/Initialization/Updates/UpdateVersion.cs +++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs @@ -397,9 +397,24 @@ public enum UpdateVersion /// The version of the . /// FixAreaSkills = 78, - + /// /// The version of the . /// FinishDarkLordMasterTree = 79, + + /// + /// The version of the . + /// + FinishDarkKnightMasterTree075 = 80, + + /// + /// The version of the . + /// + FinishDarkKnightMasterTree095d = 81, + + /// + /// The version of the . + /// + FinishDarkKnightMasterTreeSeason6 = 82, } diff --git a/src/Persistence/Initialization/VersionSeasonSix/Items/HarmonyOptions.cs b/src/Persistence/Initialization/VersionSeasonSix/Items/HarmonyOptions.cs index 7096970d2..c04203442 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/Items/HarmonyOptions.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/Items/HarmonyOptions.cs @@ -102,7 +102,7 @@ private void CreatePhysicalAttackOptions() definition.PossibleOptions.Add(this.CreateHarmonyOptions(2, ItemOptionDefinitionNumbers.HarmonyPhysical, 40, Stats.MaximumPhysBaseDmg, AggregateType.AddRaw, 0, 3, 4, 5, 6, 7, 8, 10, 12, 14, 17, 20, 23, 26, 29)); definition.PossibleOptions.Add(this.CreateHarmonyOptions(3, ItemOptionDefinitionNumbers.HarmonyPhysical, 40, Stats.RequiredStrengthReduction, AggregateType.AddRaw, 0, 6, 8, 10, 12, 14, 16, 20, 23, 26, 29, 32, 35, 37, 40)); definition.PossibleOptions.Add(this.CreateHarmonyOptions(4, ItemOptionDefinitionNumbers.HarmonyPhysical, 40, Stats.RequiredAgilityReduction, AggregateType.AddRaw, 0, 6, 8, 10, 12, 14, 16, 20, 23, 26, 29, 32, 35, 37, 40)); - definition.PossibleOptions.Add(this.CreateHarmonyOptions(5, ItemOptionDefinitionNumbers.HarmonyPhysical, 30, Stats.PhysicalBaseDmg, AggregateType.AddRaw, 6, 7, 8, 9, 11, 12, 14, 16, 19)); + definition.PossibleOptions.Add(this.CreateHarmonyOptions(5, ItemOptionDefinitionNumbers.HarmonyPhysical, 30, Stats.PhysicalBaseDmg, AggregateType.AddFinal, 6, 7, 8, 9, 11, 12, 14, 16, 19)); definition.PossibleOptions.Add(this.CreateHarmonyOptions(6, ItemOptionDefinitionNumbers.HarmonyPhysical, 30, Stats.CriticalDamageBonus, AggregateType.AddRaw, 6, 12, 14, 16, 18, 20, 22, 24, 30)); definition.PossibleOptions.Add(this.CreateHarmonyOptions(7, ItemOptionDefinitionNumbers.HarmonyPhysical, 20, Stats.SkillDamageBonus, AggregateType.AddRaw, 9, 12, 14, 16, 18, 22)); definition.PossibleOptions.Add(this.CreateHarmonyOptions(8, ItemOptionDefinitionNumbers.HarmonyPhysical, 20, Stats.AttackRatePvp, AggregateType.AddRaw, 9, 5, 7, 9, 11, 14)); diff --git a/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs b/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs index 898571207..421677474 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs @@ -303,7 +303,7 @@ private void AddFenrirOptions(ItemDefinition fenrir) fenrirOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(ItemOptionTypes.GoldFenrir, 4, Stats.MaximumHealth, AggregateType.AddFinal, ItemOptionDefinitionNumbers.Fenrir, 0, (Stats.TotalLevel, 0.5f))); fenrirOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(ItemOptionTypes.GoldFenrir, 4, Stats.MaximumMana, AggregateType.AddFinal, ItemOptionDefinitionNumbers.Fenrir, 0, (Stats.TotalLevel, 0.5f))); - fenrirOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(ItemOptionTypes.GoldFenrir, 4, Stats.PhysicalBaseDmg, AggregateType.AddRaw, ItemOptionDefinitionNumbers.Fenrir, 0, (Stats.TotalLevel, 1f / 12f))); + fenrirOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(ItemOptionTypes.GoldFenrir, 4, Stats.PhysicalBaseDmg, AggregateType.AddFinal, ItemOptionDefinitionNumbers.Fenrir, 0, (Stats.TotalLevel, 1f / 12f))); fenrirOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(ItemOptionTypes.GoldFenrir, 4, Stats.WizardryBaseDmg, AggregateType.AddRaw, ItemOptionDefinitionNumbers.Fenrir, 0, (Stats.TotalLevel, 1f / 25f))); fenrir.PossibleItemOptions.Add(fenrirOptionDefinition); diff --git a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs index 9f544a025..43b93cd1a 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs @@ -78,6 +78,7 @@ internal class SkillsInitializer : SkillsInitializerBase { SkillNumber.FireBurstMastery, MagicEffectNumber.Stunned }, { SkillNumber.EarthshakeMastery, MagicEffectNumber.Stunned }, { SkillNumber.CritDmgIncPowUp3, MagicEffectNumber.CriticalDamageIncreaseMastery }, + { SkillNumber.SwellLifeProficiency, MagicEffectNumber.GreaterFortitudeProficiency }, }; private readonly IDictionary _masterSkillRoots; @@ -652,6 +653,9 @@ private void InitializeSkillAttributes() this.AddAttributeRelationship(SkillNumber.DragonRoar, Stats.SkillFinalMultiplier, 1.0f, Stats.SkillMultiplier); this.AddAttributeRelationship(SkillNumber.DragonSlasher, Stats.SkillFinalMultiplier, 1.0f, Stats.SkillMultiplier); + + // Other + this.AddAttributeRelationship(SkillNumber.RagefulBlowMastery, Stats.RagefulBlowMasteryDurabilityDecChance, 1, Stats.RagefulBlowMasteryDurabilityDecChance); } private void AddAttributeRelationship(SkillNumber skillNumber, AttributeDefinition targetAttribute, float multiplier, AttributeDefinition sourceAttribute, InputOperator inputOperator = InputOperator.Multiply, AggregateType aggregateType = AggregateType.AddRaw) @@ -694,6 +698,7 @@ private void InitializeEffects() new RequiemEffectInitializer(this.Context, this.GameConfiguration).Initialize(); new StunEffectInitializer(this.Context, this.GameConfiguration).Initialize(); new CriticalDamageIncreaseMasteryEffectInitializer(this.Context, this.GameConfiguration).Initialize(); + new LifeSwellProficiencyEffectInitializer(this.Context, this.GameConfiguration).Initialize(); } private void MapSkillsToEffects() @@ -763,8 +768,8 @@ private void InitializeMasterSkillData() this.AddMasterSkillDefinition(SkillNumber.LungeStrengthener, SkillNumber.Lunge, SkillNumber.Undefined, 2, 2, SkillNumber.Lunge, 20, Formula632); this.AddMasterSkillDefinition(SkillNumber.TwistingSlashStreng, SkillNumber.TwistingSlash, SkillNumber.Undefined, 2, 3, SkillNumber.TwistingSlash, 20, Formula632); this.AddMasterSkillDefinition(SkillNumber.RagefulBlowStreng, SkillNumber.RagefulBlow, SkillNumber.Undefined, 2, 3, SkillNumber.RagefulBlow, 20, Formula502); - this.AddMasterSkillDefinition(SkillNumber.TwistingSlashMastery, SkillNumber.TwistingSlashStreng, SkillNumber.Undefined, 2, 4, SkillNumber.TwistingSlash, 20, Formula120); - this.AddMasterSkillDefinition(SkillNumber.RagefulBlowMastery, SkillNumber.RagefulBlowStreng, SkillNumber.Undefined, 2, 4, SkillNumber.RagefulBlow, 20, Formula120); + this.AddMasterSkillDefinition(SkillNumber.TwistingSlashMastery, SkillNumber.TwistingSlashStreng, SkillNumber.Undefined, 2, 4, SkillNumber.TwistingSlashStreng, 20, $"{Formula120} / 100", Formula120, Stats.MasteryMoveTargetChance, AggregateType.AddRaw); + this.AddMasterSkillDefinition(SkillNumber.RagefulBlowMastery, SkillNumber.RagefulBlowStreng, SkillNumber.Undefined, 2, 4, SkillNumber.RagefulBlowStreng, 20, $"{Formula120} / 100", Formula120, Stats.RagefulBlowMasteryDurabilityDecChance, AggregateType.AddRaw); this.AddPassiveMasterSkillDefinition(SkillNumber.MaximumLifeIncrease, Stats.MaximumHealth, AggregateType.AddRaw, Formula10235, 4, 2); this.AddPassiveMasterSkillDefinition(SkillNumber.WeaponMasteryBladeMaster, Stats.MasterSkillPhysBonusDmg, AggregateType.AddRaw, Formula502, 4, 2); this.AddMasterSkillDefinition(SkillNumber.DeathStabStrengthener, SkillNumber.DeathStab, SkillNumber.Undefined, 2, 5, SkillNumber.DeathStab, 20, Formula502); @@ -777,18 +782,13 @@ private void InitializeMasterSkillData() this.AddPassiveMasterSkillDefinition(SkillNumber.SpearStrengthener, Stats.SpearBonusDamage, AggregateType.AddRaw, Formula632, 2, 3); this.AddPassiveMasterSkillDefinition(SkillNumber.TwoHandedSwordMaster, Stats.TwoHandedSwordMasteryBonusDamage, AggregateType.AddRaw, Formula1154, 3, 3, SkillNumber.TwoHandedSwordStrengthener); this.AddPassiveMasterSkillDefinition(SkillNumber.OneHandedSwordMaster, Stats.WeaponMasteryAttackSpeed, AggregateType.AddRaw, Formula1, 3, 3, SkillNumber.OneHandedSwordStrengthener, SkillNumber.Undefined, 10); - - // todo: Probability of stunning the target for 2 seconds according to the assigned Skill Level while using a Mace. - this.AddMasterSkillDefinition(SkillNumber.MaceMastery, SkillNumber.MaceStrengthener, SkillNumber.Undefined, 3, 3, SkillNumber.Undefined, 20, Formula120); - - // todo: Increases the probability of Double Damage while using a Spear according to the assigned Skill Level. - this.AddMasterSkillDefinition(SkillNumber.SpearMastery, SkillNumber.SpearStrengthener, SkillNumber.Undefined, 3, 3, SkillNumber.Undefined, 20, Formula120); - - this.AddMasterSkillDefinition(SkillNumber.SwellLifeStrengt, SkillNumber.SwellLife, SkillNumber.Undefined, 3, 4, SkillNumber.SwellLife, 20, Formula181); + this.AddPassiveMasterSkillDefinition(SkillNumber.MaceMastery, Stats.MasteryStunChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 3, SkillNumber.MaceStrengthener); + this.AddPassiveMasterSkillDefinition(SkillNumber.SpearMastery, Stats.SpearMasteryDoubleDamageChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 3, SkillNumber.SpearStrengthener); + this.AddMasterSkillDefinition(SkillNumber.SwellLifeStrengt, SkillNumber.SwellLife, SkillNumber.Undefined, 3, 4, SkillNumber.SwellLife, 20, $"{Formula181} / 100", Formula181, Stats.SwellLifeHealthIncrease, AggregateType.AddRaw); this.AddPassiveMasterSkillDefinition(SkillNumber.ManaReduction, Stats.ManaUsageReduction, AggregateType.AddRaw, Formula722Value, Formula722, 4, 3); this.AddPassiveMasterSkillDefinition(SkillNumber.MonsterAttackSdInc, Stats.ShieldAfterMonsterKillMultiplier, AggregateType.AddFinal, Formula914, 4, 3); this.AddPassiveMasterSkillDefinition(SkillNumber.MonsterAttackLifeInc, Stats.HealthAfterMonsterKillMultiplier, AggregateType.AddFinal, Formula4319, 4, 3); - this.AddMasterSkillDefinition(SkillNumber.SwellLifeProficiency, SkillNumber.SwellLifeStrengt, SkillNumber.Undefined, 3, 5, SkillNumber.SwellLife, 20, Formula181); + this.AddMasterSkillDefinition(SkillNumber.SwellLifeProficiency, SkillNumber.SwellLifeStrengt, SkillNumber.Undefined, 3, 5, SkillNumber.SwellLifeStrengt, 20, $"{Formula181} / 100", Formula181, Stats.SwellLifeManaIncrease, AggregateType.AddRaw); this.AddPassiveMasterSkillDefinition(SkillNumber.MinimumAttackPowerInc, Stats.MinimumPhysBaseDmg, AggregateType.AddRaw, Formula502, 5, 3); this.AddPassiveMasterSkillDefinition(SkillNumber.MonsterAttackManaInc, Stats.ManaAfterMonsterKillMultiplier, AggregateType.AddFinal, Formula4319, 5, 3, SkillNumber.MonsterAttackLifeInc); @@ -842,8 +842,8 @@ private void InitializeMasterSkillData() this.AddPassiveMasterSkillDefinition(SkillNumber.WindTomeStrengthener, Stats.RequiemBonusDmg, AggregateType.AddRaw, Formula632, 2, 2); this.AddPassiveMasterSkillDefinition(SkillNumber.LightningTomeStren, Stats.PollutionBonusDmg, AggregateType.AddRaw, Formula632, 2, 2); this.AddPassiveMasterSkillDefinition(SkillNumber.FireTomeMastery, Stats.BleedingDamageMultiplier, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.FireTomeStrengthener); - this.AddPassiveMasterSkillDefinition(SkillNumber.WindTomeMastery, Stats.StunChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 2, SkillNumber.WindTomeStrengthener); - this.AddPassiveMasterSkillDefinition(SkillNumber.LightningTomeMastery, Stats.PollutionMoveTargetChance, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.LightningTomeStren); + this.AddPassiveMasterSkillDefinition(SkillNumber.WindTomeMastery, Stats.MasteryStunChance, AggregateType.AddRaw, $"{Formula120} / 100", 3, 2, SkillNumber.WindTomeStrengthener); + this.AddPassiveMasterSkillDefinition(SkillNumber.LightningTomeMastery, Stats.MasteryMoveTargetChance, AggregateType.AddRaw, $"{Formula181} / 100", 3, 2, SkillNumber.LightningTomeStren); this.AddMasterSkillDefinition(SkillNumber.SleepStrengthener, SkillNumber.Sleep, SkillNumber.Undefined, 2, 3, SkillNumber.Sleep, 20, Formula120); this.AddMasterSkillDefinition(SkillNumber.ChainLightningStr, SkillNumber.ChainLightning, SkillNumber.Undefined, 2, 4, SkillNumber.ChainLightning, 20, Formula502); this.AddMasterSkillDefinition(SkillNumber.LightningShockStr, SkillNumber.LightningShock, SkillNumber.Undefined, 2, 4, SkillNumber.LightningShock, 20, Formula502); @@ -878,10 +878,10 @@ private void InitializeMasterSkillData() this.AddMasterSkillDefinition(SkillNumber.CriticalDmgIncPowUp, SkillNumber.IncreaseCriticalDamage, SkillNumber.Undefined, 2, 3, SkillNumber.IncreaseCriticalDamage, 20, Formula632, Formula632, Stats.CriticalDamageBonus, AggregateType.AddRaw); this.AddMasterSkillDefinition(SkillNumber.EarthshakeStreng, SkillNumber.Earthshake, SkillNumber.DarkHorseStreng1, 2, 3, SkillNumber.Earthshake, 20, Formula502); this.AddPassiveMasterSkillDefinition(SkillNumber.WeaponMasteryLordEmperor, Stats.MasterSkillPhysBonusDmg, AggregateType.AddRaw, Formula502, 3, 2); - this.AddMasterSkillDefinition(SkillNumber.FireBurstMastery, SkillNumber.FireBurstStreng, SkillNumber.Undefined, 2, 4, SkillNumber.FireBurstStreng, 20, $"{Formula120} / 100", Formula120, Stats.StunChance, AggregateType.AddRaw); + this.AddMasterSkillDefinition(SkillNumber.FireBurstMastery, SkillNumber.FireBurstStreng, SkillNumber.Undefined, 2, 4, SkillNumber.FireBurstStreng, 20, $"{Formula120} / 100", Formula120, Stats.MasteryStunChance, AggregateType.AddRaw); this.AddMasterSkillDefinition(SkillNumber.CritDmgIncPowUp2, SkillNumber.CriticalDmgIncPowUp, SkillNumber.Undefined, 2, 4, SkillNumber.CriticalDmgIncPowUp, 20, Formula803, true); - this.AddMasterSkillDefinition(SkillNumber.EarthshakeMastery, SkillNumber.EarthshakeStreng, SkillNumber.Undefined, 2, 4, SkillNumber.EarthshakeStreng, 20, $"{Formula120} / 100", Formula120, Stats.StunChance, AggregateType.AddRaw); - this.AddMasterSkillDefinition(SkillNumber.CritDmgIncPowUp3, SkillNumber.CritDmgIncPowUp2, SkillNumber.Undefined, 2, 5, SkillNumber.CritDmgIncPowUp2, 20, $"{Formula181} / 100", Formula181, Stats.CriticalDamageChance, AggregateType.AddRaw); + this.AddMasterSkillDefinition(SkillNumber.EarthshakeMastery, SkillNumber.EarthshakeStreng, SkillNumber.Undefined, 2, 4, SkillNumber.EarthshakeStreng, 20, $"{Formula120} / 100", Formula120, Stats.MasteryStunChance, AggregateType.AddRaw); + this.AddMasterSkillDefinition(SkillNumber.CritDmgIncPowUp3, SkillNumber.CritDmgIncPowUp2, SkillNumber.Undefined, 2, 5, SkillNumber.CritDmgIncPowUp2, 20, $"{Formula181} / 100", Formula181, Stats.CriticalDamageChance, AggregateType.AddRaw); this.AddMasterSkillDefinition(SkillNumber.FireScreamStren, SkillNumber.FireScream, SkillNumber.Undefined, 2, 5, SkillNumber.FireScream, 20, Formula502); this.AddPassiveMasterSkillDefinition(SkillNumber.DarkSpiritStr, Stats.RavenBonusDamage, AggregateType.AddRaw, Formula632, 2, 3); this.AddPassiveMasterSkillDefinition(SkillNumber.ScepterStrengthener, Stats.ScepterStrBonusDamage, AggregateType.AddRaw, Formula502, 2, 3); diff --git a/src/Persistence/Initialization/WingsInitializerBase.cs b/src/Persistence/Initialization/WingsInitializerBase.cs index f5d9f0f25..7302887d9 100644 --- a/src/Persistence/Initialization/WingsInitializerBase.cs +++ b/src/Persistence/Initialization/WingsInitializerBase.cs @@ -66,7 +66,7 @@ protected IEnumerable BuildOptions(params (int, OptionTyp yield return this.CreateItemOption(tuple.Item1, Stats.HealthRecoveryMultiplier, 0, AggregateType.AddRaw, 0.01f, ItemOptionDefinitionNumbers.WingHealthRecover); break; case OptionType.PhysDamage: - yield return this.CreateItemOption(tuple.Item1, Stats.PhysicalBaseDmg, 0, AggregateType.AddRaw, 4f, ItemOptionDefinitionNumbers.WingPhysical); + yield return this.CreateItemOption(tuple.Item1, Stats.PhysicalBaseDmg, 0, AggregateType.AddFinal, 4f, ItemOptionDefinitionNumbers.WingPhysical); break; case OptionType.WizDamage: yield return this.CreateItemOption(tuple.Item1, Stats.WizardryBaseDmg, 0, AggregateType.AddRaw, 4f, ItemOptionDefinitionNumbers.WingWizardry);