diff --git a/src/GameLogic/AttackableExtensions.cs b/src/GameLogic/AttackableExtensions.cs index e3e9ab0f9..5a4377795 100644 --- a/src/GameLogic/AttackableExtensions.cs +++ b/src/GameLogic/AttackableExtensions.cs @@ -861,9 +861,27 @@ private static async ValueTask ApplyMagicEffectAsync(this IAttackable target, IA var damage = (hit.HealthDamage + hit.ShieldDamage) * multiplier; magicEffect = new BleedingMagicEffect(powerUps[0].Boost, magicEffectDefinition, durationSpan, attacker, target, damage); } + 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))) + { + return; + } + + magicEffect = new MagicEffect(durationSpan, magicEffectDefinition, [.. powerUps.Where(p => p != stunChancePowerUp).Select(p => new MagicEffect.ElementWithTarget(p.Boost, p.Target))]); + } else { - magicEffect = new MagicEffect(durationSpan, magicEffectDefinition, powerUps.Select(p => new MagicEffect.ElementWithTarget(p.Boost, p.Target)).ToArray()); + magicEffect = new MagicEffect(durationSpan, magicEffectDefinition, [.. powerUps.Select(p => new MagicEffect.ElementWithTarget(p.Boost, p.Target))]); + } + + if (magicEffect.Definition.SubType > 0 + && await target.MagicEffectList.TryGetActiveEffectOfSubTypeAsync(magicEffect.Definition.SubType).ConfigureAwait(false) is { } existingEffect + && existingEffect.Id != magicEffect.Id) + { + // The new effect replaces an existing effect with a different number + await existingEffect.DisposeAsync().ConfigureAwait(false); } await target.MagicEffectList.AddEffectAsync(magicEffect).ConfigureAwait(false); diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index d214fb407..e07462a4d 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -1633,7 +1633,7 @@ void AddSkillPowersToResult(ICollection powerUps, ref (Attrib var extendsDuration = masterSkillEntry.Skill?.MasterDefinition?.ExtendsDuration ?? false; if (extendsDuration && !durationExtended) { - durationElement = new CombinedElement(durationElement, new ConstantElement(skillEntry.CalculateValue())); + durationElement = new CombinedElement(durationElement, new ConstantElement(masterSkillEntry.CalculateValue())); } else if (extendsDuration) { diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs index 0bcbdc334..992abdcdd 100644 --- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs +++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs @@ -1,4 +1,4 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -6,7 +6,6 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; - using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic.Attributes; using MUnique.OpenMU.GameLogic.NPC; @@ -21,6 +20,7 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills; public class AreaSkillAttackAction { private const int UndefinedTarget = 0xFFFF; + private const short ElectricSpikeSkillId = 65; private static readonly ConcurrentDictionary FrustumFilters = new(); @@ -68,6 +68,7 @@ private static bool AreaSkillSettingsAreDefault([NotNullWhen(true)] AreaSkillSet && settings.DelayPerOneDistance <= TimeSpan.Zero && settings.MinimumNumberOfHitsPerTarget == 1 && settings.MaximumNumberOfHitsPerTarget == 1 + && settings.MinimumNumberOfHitsPerAttack == 0 && settings.MaximumNumberOfHitsPerAttack == 0 && Math.Abs(settings.HitChancePerDistanceMultiplier - 1.0) <= 0.00001f; } @@ -335,6 +336,18 @@ private async ValueTask PerformAutomaticHitsAsync(Player player, ushort extraTar } } + if (skillEntry.Skill?.Number == ElectricSpikeSkillId && attackCount > 0 && player.Attributes![Stats.NearbyPartyMemberCount] > 0) + { + foreach (var partyMember in player.Party?.PartyList.OfType().Where(m => m.Observers.Contains(player)) ?? []) + { + if (partyMember.Attributes is { } memberAttributes) + { + memberAttributes[Stats.CurrentHealth] *= 0.8f; + memberAttributes[Stats.CurrentMana] *= 0.95f; + } + } + } + return extraTarget; } diff --git a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs index 90a0b09d8..926f1067a 100644 --- a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs @@ -40,7 +40,7 @@ public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackab } var currentDistance = startingPoint.EuclideanDistanceTo(currentTarget); - while (currentDistance < skillEntry.Skill.Range) + for (int i = 0; i < 3; i++) { var nextTarget = currentTarget.CalculateTargetPoint(direction); if (!currentMap.Terrain.WalkMap[nextTarget.X, nextTarget.Y] diff --git a/src/GameLogic/PlayerActions/Skills/FireScreamSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/FireScreamSkillPlugIn.cs new file mode 100644 index 000000000..15ed56518 --- /dev/null +++ b/src/GameLogic/PlayerActions/Skills/FireScreamSkillPlugIn.cs @@ -0,0 +1,99 @@ +// +// 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.GuildWar; +using MUnique.OpenMU.GameLogic.NPC; +using MUnique.OpenMU.GameLogic.PlugIns; +using MUnique.OpenMU.Pathfinding; +using MUnique.OpenMU.PlugIns; + +/// +/// Handles the fire scream skill of the dark lord class. Based on a chance, it does an additional damage (explosion) to any targets in a radius which origin is the target itself. +/// +[PlugIn] +[Display(Name = nameof(PlugInResources.FireScreamSkillPlugIn_Name), Description = nameof(PlugInResources.FireScreamSkillPlugIn_Description), ResourceType = typeof(PlugInResources))] +[Guid("7E2F9B4A-D6C1-4F8E-A3B5-21E8C7F9D541")] +public class FireScreamSkillPlugIn : IAreaSkillPlugIn +{ + /// + public short Key => 78; + + private short Radius => 2; + + /// + public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo) + { + if (hitInfo is not { } hit || !Rand.NextRandomBool(0.3)) + { + return; + } + + var attackDamage = hit.HealthDamage + hit.ShieldDamage; + var explosionDamage = attackDamage / 10; + if (explosionDamage < 1) + { + return; + } + + bool FilterTarget(IAttackable attackable) + { + if (attackable == attacker) + { + return false; + } + + if (attackable is Monster { SummonedBy: null } or Destructible) + { + return true; + } + + if (attackable is Monster { SummonedBy: not null } summoned) + { + return FilterTarget(summoned.SummonedBy); + } + + if (attackable is Player { DuelRoom.State: DuelState.DuelStarted } targetPlayer + && attacker is Player { DuelRoom.State: DuelState.DuelStarted } duelPlayer + && targetPlayer.DuelRoom == duelPlayer.DuelRoom + && targetPlayer.DuelRoom.IsDuelist(targetPlayer) && targetPlayer.DuelRoom.IsDuelist(duelPlayer)) + { + return true; + } + + if (attackable is Player { GuildWarContext.State: GuildWarState.Started } guildWarTarget + && attacker is Player { GuildWarContext.State: GuildWarState.Started } guildWarAttacker + && guildWarTarget.GuildWarContext == guildWarAttacker.GuildWarContext) + { + return true; + } + + return false; + } + + var explosionTargets = target.CurrentMap? + .GetAttackablesInRange(target.Position, this.Radius) + .Where(a => a.GetDistanceTo(target) <= this.Radius) + .Where(FilterTarget) ?? []; + if (!explosionTargets.Any()) + { + return; + } + + // Delay the explosion a little bit, so the client can show the hit values staggered + await Task.Delay(100).ConfigureAwait(false); + + foreach (var explosionTarget in explosionTargets) + { + if (explosionTarget.IsActive()) + { + // We just need to apply the damage, so we can resort to the bleeding damage method which has DamageAttributes.Undefined + await explosionTarget.ApplyBleedingDamageAsync(attacker, explosionDamage).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 61250d3ed..b5d5b231d 100644 --- a/src/GameLogic/Properties/PlugInResources.Designer.cs +++ b/src/GameLogic/Properties/PlugInResources.Designer.cs @@ -591,6 +591,24 @@ public static string EndDuelWhenLeavingDuelMapPlugIn_Name { } } + /// + /// Looks up a localized string similar to Handles the fire scream skill of the dark lord class. Based on a chance, it does an additional damage (explosion) to any targets in a radius which origin is the target itself.. + /// + public static string FireScreamSkillPlugIn_Description { + get { + return ResourceManager.GetString("FireScreamSkillPlugIn_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fire scream skill. + /// + public static string FireScreamSkillPlugIn_Name { + get { + return ResourceManager.GetString("FireScreamSkillPlugIn_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Handles the force skill of the dark lord.. /// diff --git a/src/GameLogic/Properties/PlugInResources.resx b/src/GameLogic/Properties/PlugInResources.resx index cf2b035bf..f31d436ba 100644 --- a/src/GameLogic/Properties/PlugInResources.resx +++ b/src/GameLogic/Properties/PlugInResources.resx @@ -387,6 +387,12 @@ Handles the earth shake skill of the dark horse. Pushes the targets away from the attacker. + + Fire scream skill + + + Handles the fire scream skill of the dark lord class. Based on a chance, it does an additional damage (explosion) to any targets in a radius which origin is the target itself. + ForceSkillAction diff --git a/src/Persistence/Initialization/Skills/CriticalDamageIncreaseEffectInitializer.cs b/src/Persistence/Initialization/Skills/CriticalDamageIncreaseEffectInitializer.cs index 977fe2337..24d2f92ec 100644 --- a/src/Persistence/Initialization/Skills/CriticalDamageIncreaseEffectInitializer.cs +++ b/src/Persistence/Initialization/Skills/CriticalDamageIncreaseEffectInitializer.cs @@ -32,10 +32,12 @@ public override void Initialize() magicEffect.Number = (byte)MagicEffectNumber.CriticalDamageIncrease; magicEffect.Name = "Critical Damage Increase Skill Effect"; magicEffect.InformObservers = true; + magicEffect.SubType = 17; 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); diff --git a/src/Persistence/Initialization/Skills/CriticalDamageIncreaseMasteryEffectInitializer.cs b/src/Persistence/Initialization/Skills/CriticalDamageIncreaseMasteryEffectInitializer.cs new file mode 100644 index 000000000..33fff0a60 --- /dev/null +++ b/src/Persistence/Initialization/Skills/CriticalDamageIncreaseMasteryEffectInitializer.cs @@ -0,0 +1,77 @@ +// +// 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 critical damage increase mastery effect. +/// +public class CriticalDamageIncreaseMasteryEffectInitializer : InitializerBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The game configuration. + public CriticalDamageIncreaseMasteryEffectInitializer(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.CriticalDamageIncreaseMastery; + magicEffect.Name = "Critical Damage Increase Mastery Skill Effect"; + + var critDmgIncEffect = this.GameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.CriticalDamageIncrease); + magicEffect.InformObservers = critDmgIncEffect.InformObservers; + magicEffect.SubType = critDmgIncEffect.SubType; + magicEffect.SendDuration = critDmgIncEffect.SendDuration; + magicEffect.StopByDeath = critDmgIncEffect.StopByDeath; + magicEffect.Duration = this.Context.CreateNew(); + magicEffect.Duration.ConstantValue.Value = critDmgIncEffect.Duration!.ConstantValue.Value; + magicEffect.Duration.MaximumValue = critDmgIncEffect.Duration.MaximumValue; + + foreach (var durationRelatedValue in critDmgIncEffect.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 critDmgIncEffect.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); + } + } + + var critChancePowerUpDefinition = this.Context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(critChancePowerUpDefinition); + critChancePowerUpDefinition.TargetAttribute = Stats.CriticalDamageChance.GetPersistent(this.GameConfiguration); + critChancePowerUpDefinition.Boost = this.Context.CreateNew(); + critChancePowerUpDefinition.Boost.ConstantValue.Value = 0; + } +} \ No newline at end of file diff --git a/src/Persistence/Initialization/Skills/MagicEffectNumber.cs b/src/Persistence/Initialization/Skills/MagicEffectNumber.cs index 2b478ac3d..a38d8cb9f 100644 --- a/src/Persistence/Initialization/Skills/MagicEffectNumber.cs +++ b/src/Persistence/Initialization/Skills/MagicEffectNumber.cs @@ -326,6 +326,11 @@ internal enum MagicEffectNumber : short /// WizEnhance3 = 139, + /// + /// The critical damage increase mastery effect. + /// + CriticalDamageIncreaseMastery = 148, + #region Artificial effects which are not sent to the client, starting at 200. /// diff --git a/src/Persistence/Initialization/Skills/StunEffectInitializer.cs b/src/Persistence/Initialization/Skills/StunEffectInitializer.cs index 0433768ee..d27f58abb 100644 --- a/src/Persistence/Initialization/Skills/StunEffectInitializer.cs +++ b/src/Persistence/Initialization/Skills/StunEffectInitializer.cs @@ -33,11 +33,20 @@ public override void Initialize() magicEffect.InformObservers = true; magicEffect.SendDuration = false; magicEffect.StopByDeath = true; + magicEffect.Duration = this.Context.CreateNew(); + magicEffect.Duration.ConstantValue.Value = 2; // 2 seconds var isStunnedPowerUpDefinition = this.Context.CreateNew(); magicEffect.PowerUpDefinitions.Add(isStunnedPowerUpDefinition); isStunnedPowerUpDefinition.TargetAttribute = Stats.IsStunned.GetPersistent(this.GameConfiguration); isStunnedPowerUpDefinition.Boost = this.Context.CreateNew(); isStunnedPowerUpDefinition.Boost.ConstantValue.Value = 1; + + // 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.Boost = this.Context.CreateNew(); + stunChancePowerUpDefinition.Boost.ConstantValue.Value = 0; } } \ No newline at end of file diff --git a/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs b/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs new file mode 100644 index 000000000..e354fd544 --- /dev/null +++ b/src/Persistence/Initialization/Updates/FinishDarkLordMasterTreePlugIn.cs @@ -0,0 +1,230 @@ +// +// 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 lord master tree skills and effects. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("1A2B3C4D-5E6F-7890-ABCD-EF1234567890")] +public class FinishDarkLordMasterTreePlugIn : UpdatePlugInBase +{ + /// + /// The plug in name. + /// + internal const string PlugInName = "Finish Dark Lord Master Tree PlugIn"; + + /// + /// The plug in description. + /// + internal const string PlugInDescription = "This update completes the dark lord master tree skills and effects."; + + /// + public override UpdateVersion Version => UpdateVersion.FinishDarkLordMasterTree; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override bool IsMandatory => true; + + /// + public override DateTime CreatedAt => new(2026, 4, 14, 16, 0, 0, DateTimeKind.Utc); + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + // Create Critical Damage Increase Mastery Skill Effect + var critDmgIncMasteryEffect = this.CreateCritDmgIncMasteryEffect(context, gameConfiguration); + + // Update Stunned effect + var stunnedEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.Stunned); + stunnedEffect.Duration = context.CreateNew(); + stunnedEffect.Duration.ConstantValue.Value = 2; + + var stunChancePowerUpDefinition = context.CreateNew(); + stunnedEffect.PowerUpDefinitions.Add(stunChancePowerUpDefinition); + stunChancePowerUpDefinition.TargetAttribute = Stats.StunChance.GetPersistent(gameConfiguration); + stunChancePowerUpDefinition.Boost = context.CreateNew(); + stunChancePowerUpDefinition.Boost.ConstantValue.Value = 0; + + // Map skills to effects + this.MapSkillToEffect(gameConfiguration, SkillNumber.FireBurstMastery, stunnedEffect); + this.MapSkillToEffect(gameConfiguration, SkillNumber.EarthshakeMastery, stunnedEffect); + this.MapSkillToEffect(gameConfiguration, SkillNumber.CritDmgIncPowUp3, critDmgIncMasteryEffect); + + // Update AreaSkillSettings + this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Earthshake, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15); + this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.EarthshakeStreng, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15); + this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.EarthshakeMastery, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15); + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChaoticDiseier) is { } chaoticDiseier) + { + chaoticDiseier.AreaSkillSettings!.MinimumNumberOfHitsPerAttack = 7; + } + + // Update master skills + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CriticalDmgIncPowUp)?.MasterDefinition is { } fireTomeMastery) + { + fireTomeMastery.TargetAttribute = Stats.CriticalDamageBonus.GetPersistent(gameConfiguration); + fireTomeMastery.Aggregation = AggregateType.AddRaw; + } + + 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.Aggregation = AggregateType.AddRaw; + fireBurstMastery.ValueFormula = $"{fireBurstMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CritDmgIncPowUp2)?.MasterDefinition is { } critDmgIncPowUp2) + { + critDmgIncPowUp2.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.CriticalDmgIncPowUp); + critDmgIncPowUp2.ExtendsDuration = true; + } + + 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.Aggregation = AggregateType.AddRaw; + earthshakeMastery.ValueFormula = $"{earthshakeMastery.ValueFormula} / 100"; + } + + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CritDmgIncPowUp3)?.MasterDefinition is { } critDmgIncPowUp3) + { + critDmgIncPowUp3.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.CritDmgIncPowUp2); + critDmgIncPowUp3.TargetAttribute = Stats.CriticalDamageChance.GetPersistent(gameConfiguration); + critDmgIncPowUp3.Aggregation = AggregateType.AddRaw; + critDmgIncPowUp3.ValueFormula = $"{critDmgIncPowUp3.ValueFormula} / 100"; + } + } + + private MagicEffectDefinition CreateCritDmgIncMasteryEffect(IContext context, GameConfiguration gameConfiguration) + { + var magicEffect = context.CreateNew(); + gameConfiguration.MagicEffects.Add(magicEffect); + magicEffect.Number = (byte)MagicEffectNumber.CriticalDamageIncreaseMastery; + magicEffect.Name = "Critical Damage Increase Mastery Skill Effect"; + + var critDmgIncEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.CriticalDamageIncrease); + critDmgIncEffect.Duration?.MaximumValue = 180; + critDmgIncEffect.SubType = 17; + magicEffect.InformObservers = critDmgIncEffect.InformObservers; + magicEffect.SubType = critDmgIncEffect.SubType; + magicEffect.SendDuration = critDmgIncEffect.SendDuration; + magicEffect.StopByDeath = critDmgIncEffect.StopByDeath; + magicEffect.Duration = context.CreateNew(); + magicEffect.Duration.ConstantValue.Value = critDmgIncEffect.Duration!.ConstantValue.Value; + magicEffect.Duration.MaximumValue = critDmgIncEffect.Duration.MaximumValue; + + foreach (var durationRelatedValue in critDmgIncEffect.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 critDmgIncEffect.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); + } + } + + var critChancePowerUpDefinition = context.CreateNew(); + magicEffect.PowerUpDefinitions.Add(critChancePowerUpDefinition); + critChancePowerUpDefinition.TargetAttribute = Stats.CriticalDamageChance.GetPersistent(gameConfiguration); + critChancePowerUpDefinition.Boost = context.CreateNew(); + critChancePowerUpDefinition.Boost.ConstantValue.Value = 0; + + return magicEffect; + } + + private void MapSkillToEffect(GameConfiguration gameConfiguration, SkillNumber skillNumber, MagicEffectDefinition magicEffect) + { + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is { } skill) + { + skill.MagicEffectDef = magicEffect; + } + } + + private void AddAreaSkillSettings( + GameConfiguration gameConfiguration, + IContext context, + SkillNumber skillNumber, + bool useFrustumFilter, + float frustumStartWidth, + float frustumEndWidth, + float frustumDistance, + bool useDeferredHits = false, + TimeSpan delayPerOneDistance = default, + TimeSpan delayBetweenHits = default, + int minimumHitsPerTarget = 1, + int maximumHitsPerTarget = 1, + int minimumHitsPerAttack = default, + int maximumHitsPerAttack = default, + float hitChancePerDistanceMultiplier = 1.0f, + bool useTargetAreaFilter = false, + float targetAreaDiameter = default, + int projectileCount = 1, + int effectRange = default) + { + if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is not { } skill) + { + return; + } + + skill.SkillType = SkillType.AreaSkillAutomaticHits; + var areaSkillSettings = context.CreateNew(); + skill.AreaSkillSettings = areaSkillSettings; + + areaSkillSettings.UseFrustumFilter = useFrustumFilter; + areaSkillSettings.FrustumStartWidth = frustumStartWidth; + areaSkillSettings.FrustumEndWidth = frustumEndWidth; + areaSkillSettings.FrustumDistance = frustumDistance; + areaSkillSettings.UseTargetAreaFilter = useTargetAreaFilter; + areaSkillSettings.TargetAreaDiameter = targetAreaDiameter; + areaSkillSettings.UseDeferredHits = useDeferredHits; + areaSkillSettings.DelayPerOneDistance = delayPerOneDistance; + areaSkillSettings.DelayBetweenHits = delayBetweenHits; + areaSkillSettings.MinimumNumberOfHitsPerTarget = minimumHitsPerTarget; + areaSkillSettings.MaximumNumberOfHitsPerTarget = maximumHitsPerTarget; + areaSkillSettings.MinimumNumberOfHitsPerAttack = minimumHitsPerAttack; + areaSkillSettings.MaximumNumberOfHitsPerAttack = maximumHitsPerAttack; + areaSkillSettings.HitChancePerDistanceMultiplier = hitChancePerDistanceMultiplier; + areaSkillSettings.ProjectileCount = projectileCount; + areaSkillSettings.EffectRange = effectRange; + } +} diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs index 3932ebc5a..9a2a531c0 100644 --- a/src/Persistence/Initialization/Updates/UpdateVersion.cs +++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs @@ -392,4 +392,9 @@ public enum UpdateVersion /// The version of the . /// FixSummonerCurseSkills = 77, + + /// + /// The version of the . + /// + FinishDarkLordMasterTree = 78, } diff --git a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs index 06bbd91af..57afbf1e6 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs @@ -75,6 +75,9 @@ internal class SkillsInitializer : SkillsInitializerBase { SkillNumber.PhoenixShot, MagicEffectNumber.DecreaseBlock }, { SkillNumber.Explosion223, MagicEffectNumber.Explosion }, { SkillNumber.Requiem, MagicEffectNumber.Requiem }, + { SkillNumber.FireBurstMastery, MagicEffectNumber.Stunned }, + { SkillNumber.EarthshakeMastery, MagicEffectNumber.Stunned }, + { SkillNumber.CritDmgIncPowUp3, MagicEffectNumber.CriticalDamageIncreaseMastery }, }; private readonly IDictionary _masterSkillRoots; @@ -165,6 +168,7 @@ public override void Initialize() this.CreateSkill(SkillNumber.Force, "Force", CharacterClasses.AllLords, DamageType.Physical, 10, 4, manaConsumption: 10); this.CreateSkill(SkillNumber.FireBurst, "Fire Burst", CharacterClasses.AllLords, DamageType.Physical, 100, 6, manaConsumption: 25, energyRequirement: 79, skillTarget: SkillTarget.ExplicitWithImplicitInRange, implicitTargetRange: 1); this.CreateSkill(SkillNumber.Earthshake, "Earthshake", CharacterClasses.AllLords, DamageType.Physical, 150, 10, 50, elementalModifier: ElementalType.Lightning, skillType: SkillType.AreaSkillAutomaticHits); + this.AddAreaSkillSettings(SkillNumber.Earthshake, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15); this.CreateSkill(SkillNumber.Summon, "Summon", CharacterClasses.AllLords, abilityConsumption: 30, manaConsumption: 70, energyRequirement: 153, leadershipRequirement: 400, skillType: SkillType.Other); this.CreateSkill(SkillNumber.IncreaseCriticalDamage, "Increase Critical Damage", CharacterClasses.AllLords, abilityConsumption: 50, manaConsumption: 50, energyRequirement: 102, leadershipRequirement: 300, skillType: SkillType.Buff, skillTarget: SkillTarget.ImplicitParty); this.CreateSkill(SkillNumber.ElectricSpike, "Electric Spike", CharacterClasses.AllLords, DamageType.Physical, 250, 10, 100, energyRequirement: 126, leadershipRequirement: 340, skillType: SkillType.AreaSkillAutomaticHits); @@ -183,7 +187,7 @@ public override void Initialize() this.CreateSkill(SkillNumber.PlasmaStorm, "Plasma Storm", CharacterClasses.AllMastersAndSecondClass, DamageType.Fenrir, damage: 60, distance: 6, abilityConsumption: 20, manaConsumption: 50, levelRequirement: 110, skillType: SkillType.AreaSkillAutomaticHits); this.CreateSkill(SkillNumber.InfinityArrow, "Infinity Arrow", CharacterClasses.MuseElfAndHighElf, distance: 6, abilityConsumption: 10, manaConsumption: 50, levelRequirement: 220, skillType: SkillType.Buff, targetRestriction: SkillTargetRestriction.Self); this.CreateSkill(SkillNumber.FireScream, "Fire Scream", CharacterClasses.AllLords, DamageType.Physical, 130, 6, 10, 45, energyRequirement: 70, leadershipRequirement: 150, skillType: SkillType.AreaSkillAutomaticHits); - this.AddAreaSkillSettings(SkillNumber.FireScream, true, 2f, 3f, 6f); // TODO: Add fireScream's explosion (Explosion79) damage effect + this.AddAreaSkillSettings(SkillNumber.FireScream, true, 2f, 3f, 6f); this.CreateSkill(SkillNumber.Explosion79, "Explosion", CharacterClasses.AllLords, DamageType.Physical, distance: 2); this.CreateSkill(SkillNumber.SummonMonster, "Summon Monster", manaConsumption: 40, energyRequirement: 90); this.CreateSkill(SkillNumber.MagicAttackImmunity, "Magic Attack Immunity", manaConsumption: 40, energyRequirement: 90); @@ -220,7 +224,7 @@ public override void Initialize() this.AddAreaSkillSettings(SkillNumber.FlameStrike, true, 5f, 2f, 4f); this.CreateSkill(SkillNumber.GiganticStorm, "Gigantic Storm", CharacterClasses.AllMGs, DamageType.Wizardry, 110, 6, 10, 120, 220, 118, elementalModifier: ElementalType.Wind, skillType: SkillType.AreaSkillAutomaticHits); this.CreateSkill(SkillNumber.ChaoticDiseier, "Chaotic Diseier", CharacterClasses.AllLords, DamageType.Physical, 190, 6, 15, 50, 100, 16, skillType: SkillType.AreaSkillAutomaticHits); - this.AddAreaSkillSettings(SkillNumber.ChaoticDiseier, true, 1.5f, 1.5f, 6f); + this.AddAreaSkillSettings(SkillNumber.ChaoticDiseier, true, 1.5f, 1.5f, 6f, minimumHitsPerAttack: 7); this.CreateSkill(SkillNumber.DoppelgangerSelfExplosion, "Doppelganger Self Explosion", CharacterClasses.AllMGs, DamageType.Wizardry, 140, 3, 25, 20, 100, elementalModifier: ElementalType.Fire); this.CreateSkill(SkillNumber.KillingBlow, "Killing Blow", CharacterClasses.AllFighters, DamageType.Physical, distance: 2, manaConsumption: 9, elementalModifier: ElementalType.Earth, hitsPerAttack: 4); // 1 packet => 1*4 hits this.CreateSkill(SkillNumber.BeastUppercut, "Beast Uppercut", CharacterClasses.AllFighters, DamageType.Physical, distance: 2, manaConsumption: 9, elementalModifier: ElementalType.Fire, hitsPerAttack: 2); // 2 packets => 2*2 hits @@ -685,6 +689,7 @@ private void InitializeEffects() new ExplosionEffectInitializer(this.Context, this.GameConfiguration).Initialize(); new RequiemEffectInitializer(this.Context, this.GameConfiguration).Initialize(); new StunEffectInitializer(this.Context, this.GameConfiguration).Initialize(); + new CriticalDamageIncreaseMasteryEffectInitializer(this.Context, this.GameConfiguration).Initialize(); } private void MapSkillsToEffects() @@ -866,13 +871,13 @@ private void InitializeMasterSkillData() this.AddMasterSkillDefinition(SkillNumber.FireBurstStreng, SkillNumber.FireBurst, SkillNumber.Undefined, 2, 2, SkillNumber.FireBurst, 20, Formula502); this.AddMasterSkillDefinition(SkillNumber.ForceWaveStreng, SkillNumber.Force, SkillNumber.Undefined, 2, 2, SkillNumber.ForceWave, 20, Formula632); this.AddPassiveMasterSkillDefinition(SkillNumber.DarkHorseStreng1, Stats.BonusDefenseWithHorse, AggregateType.AddRaw, Formula1204, 2, 2); - this.AddMasterSkillDefinition(SkillNumber.CriticalDmgIncPowUp, SkillNumber.IncreaseCriticalDamage, SkillNumber.Undefined, 2, 3, SkillNumber.IncreaseCriticalDamage, 20, Formula632); + 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.FireBurst, 20, Formula120); - this.AddMasterSkillDefinition(SkillNumber.CritDmgIncPowUp2, SkillNumber.CriticalDmgIncPowUp, SkillNumber.Undefined, 2, 4, SkillNumber.IncreaseCriticalDamage, 20, Formula803); - this.AddMasterSkillDefinition(SkillNumber.EarthshakeMastery, SkillNumber.EarthshakeStreng, SkillNumber.Undefined, 2, 4, SkillNumber.Earthshake, 20, Formula120); - this.AddMasterSkillDefinition(SkillNumber.CritDmgIncPowUp3, SkillNumber.CritDmgIncPowUp2, SkillNumber.Undefined, 2, 5, SkillNumber.IncreaseCriticalDamage, 20, Formula181); + this.AddMasterSkillDefinition(SkillNumber.FireBurstMastery, SkillNumber.FireBurstStreng, SkillNumber.Undefined, 2, 4, SkillNumber.FireBurstStreng, 20, $"{Formula120} / 100", Formula120, Stats.StunChance, 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.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); @@ -988,7 +993,7 @@ private void AddMasterSkillDefinition(SkillNumber skillNumber, SkillNumber requi skill.SkillType = replacedSkill.SkillType; skill.Target = replacedSkill.Target; skill.TargetRestriction = replacedSkill.TargetRestriction; - skill.MagicEffectDef = replacedSkill.MagicEffectDef; + skill.MagicEffectDef ??= replacedSkill.MagicEffectDef; if (replacedSkill.AreaSkillSettings is { } areaSkillSettings) {