From 141fdfcfb1999e26993d3fa39e6811ff2608b34f Mon Sep 17 00:00:00 2001 From: bernat Date: Sat, 24 Aug 2024 17:53:31 +0200 Subject: [PATCH 1/3] Implement Drain Life skill for summoner class --- src/GameLogic/IAttackable.cs | 2 +- src/GameLogic/NPC/AttackableNpcBase.cs | 6 ++- src/GameLogic/NPC/SoccerBall.cs | 3 +- src/GameLogic/Player.cs | 6 ++- .../Skills/AreaSkillAttackAction.cs | 4 +- .../Skills/ChainLightningSkillPlugIn.cs | 2 +- .../Skills/DrainLifeSkillPlugIn.cs | 38 +++++++++++++++++++ .../Skills/EarthShakeSkillPlugIn.cs | 2 +- .../Skills/PlasmaStormSkillPlugIn.cs | 2 +- src/GameLogic/PlugIns/IAreaSkillPlugIn.cs | 3 +- .../VersionSeasonSix/SkillsInitializer.cs | 2 +- 11 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs diff --git a/src/GameLogic/IAttackable.cs b/src/GameLogic/IAttackable.cs index 79172c3a9..578b81f41 100644 --- a/src/GameLogic/IAttackable.cs +++ b/src/GameLogic/IAttackable.cs @@ -65,7 +65,7 @@ public interface IAttackable : IIdentifiable, ILocateable /// The skill. /// If set to true, the attacker did a combination of skills. /// The damage factor. - ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0); + ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0); /// /// Reflects the damage which was done previously with or even to the . diff --git a/src/GameLogic/NPC/AttackableNpcBase.cs b/src/GameLogic/NPC/AttackableNpcBase.cs index ac9738b14..5dc20de6b 100644 --- a/src/GameLogic/NPC/AttackableNpcBase.cs +++ b/src/GameLogic/NPC/AttackableNpcBase.cs @@ -98,11 +98,11 @@ public int Health || (this.SpawnArea.SpawnTrigger == SpawnTrigger.AutomaticDuringWave && (this._eventStateProvider?.IsSpawnWaveActive(this.SpawnArea.WaveNumber) ?? false)); /// - public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) + public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) { if (this.Definition.ObjectKind == NpcObjectKind.Guard) { - return; + return null; } var hitInfo = await attacker.CalculateDamageAsync(this, skill, isCombo, damageFactor).ConfigureAwait(false); @@ -120,6 +120,8 @@ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool await playerSurrogate.Owner.AfterHitTargetAsync().ConfigureAwait(false); } } + + return hitInfo; } /// diff --git a/src/GameLogic/NPC/SoccerBall.cs b/src/GameLogic/NPC/SoccerBall.cs index c017fa898..deb0af2cb 100644 --- a/src/GameLogic/NPC/SoccerBall.cs +++ b/src/GameLogic/NPC/SoccerBall.cs @@ -55,10 +55,11 @@ public SoccerBall(MonsterSpawnArea spawnInfo, MonsterDefinition stats, GameMap m public DeathInformation? LastDeath => null; /// - public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) + public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) { var direction = attacker.GetDirectionTo(this); await this.MoveToDirectionAsync(direction, skill is { }).ConfigureAwait(false); + return null; } /// diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index 0fb765f67..f5b7f83c5 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -588,7 +588,7 @@ public bool IsAnySelfDefenseActive() } /// - public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) + public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0) { if (this.Attributes is null) { @@ -605,7 +605,7 @@ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool await observer.InvokeViewPlugInAsync(p => p.ShowHitAsync(this, hitInfo)).ConfigureAwait(false); } - return; + return hitInfo; } attacker.ApplyAmmunitionConsumption(hitInfo); @@ -634,6 +634,8 @@ public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool { await attackerPlayer.AfterHitTargetAsync().ConfigureAwait(false); } + + return hitInfo; } /// diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs index 5e5fedd6c..012e8cb0c 100644 --- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs +++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs @@ -129,13 +129,13 @@ private async ValueTask ApplySkillAsync(Player player, SkillEntry skillEntry, IA if (target.CheckSkillTargetRestrictions(player, skillEntry.Skill)) { - await target.AttackByAsync(player, skillEntry, isCombo).ConfigureAwait(false); + var hitInfo = await target.AttackByAsync(player, skillEntry, isCombo).ConfigureAwait(false); await target.TryApplyElementalEffectsAsync(player, skillEntry).ConfigureAwait(false); var baseSkill = skillEntry.GetBaseSkill(); if (player.GameContext.PlugInManager.GetStrategy(baseSkill.Number) is { } strategy) { - await strategy.AfterTargetGotAttackedAsync(player, target, skillEntry, targetAreaCenter).ConfigureAwait(false); + await strategy.AfterTargetGotAttackedAsync(player, target, skillEntry, targetAreaCenter, hitInfo).ConfigureAwait(false); } } } diff --git a/src/GameLogic/PlayerActions/Skills/ChainLightningSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/ChainLightningSkillPlugIn.cs index f4f6c8b6e..5f9891965 100644 --- a/src/GameLogic/PlayerActions/Skills/ChainLightningSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/ChainLightningSkillPlugIn.cs @@ -24,7 +24,7 @@ public class ChainLightningSkillPlugIn : IAreaSkillPlugIn public short Key => 215; /// - public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter) + public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo) { bool FilterTarget(IAttackable attackable) { diff --git a/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs new file mode 100644 index 000000000..05dc28838 --- /dev/null +++ b/src/GameLogic/PlayerActions/Skills/DrainLifeSkillPlugIn.cs @@ -0,0 +1,38 @@ +// +// 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.PlugIns; +using MUnique.OpenMU.GameLogic.Views.Character; +using MUnique.OpenMU.Pathfinding; +using MUnique.OpenMU.PlugIns; + +/// +/// Handles the drain life skill of the summoner class. Additionally to the attacked target, it regains life for damage dealt. +/// +[PlugIn(nameof(ChainLightningSkillPlugIn), "Handles the drain life skill of the summoner class. Additionally to the attacked target, it regains life for damage dealt.")] +[Guid("9A5A5671-3A8C-4C01-984F-1A8F8E0E7BDA")] +public class DrainLifeSkillPlugIn : IAreaSkillPlugIn +{ + /// + public short Key => 214; + + /// + public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo) + { + if (attacker is Player attackerPlayer && hitInfo != null && hitInfo.Value.HealthDamage > 0) + { + var playerAttributes = attackerPlayer.Attributes; + + if (playerAttributes != null) + { + playerAttributes[Stats.CurrentHealth] = (uint)Math.Min(playerAttributes[Stats.MaximumHealth], playerAttributes[Stats.CurrentHealth] + hitInfo.Value.HealthDamage); + await attackerPlayer.InvokeViewPlugInAsync(p => p.UpdateCurrentHealthAsync()).ConfigureAwait(false); + } + } + } +} diff --git a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs index ebd1f8384..9a5b3eb5e 100644 --- a/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/EarthShakeSkillPlugIn.cs @@ -21,7 +21,7 @@ public class EarthShakeSkillPlugIn : IAreaSkillPlugIn public short Key => 62; /// - public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter) + 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) { diff --git a/src/GameLogic/PlayerActions/Skills/PlasmaStormSkillPlugIn.cs b/src/GameLogic/PlayerActions/Skills/PlasmaStormSkillPlugIn.cs index 7cda02bed..7b2e07330 100644 --- a/src/GameLogic/PlayerActions/Skills/PlasmaStormSkillPlugIn.cs +++ b/src/GameLogic/PlayerActions/Skills/PlasmaStormSkillPlugIn.cs @@ -21,7 +21,7 @@ public class PlasmaStormSkillPlugIn : IAreaSkillPlugIn public short Key => 76; /// - public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter) + public async ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo) { if (target is Player targetPlayer && Rand.NextRandomBool(25) diff --git a/src/GameLogic/PlugIns/IAreaSkillPlugIn.cs b/src/GameLogic/PlugIns/IAreaSkillPlugIn.cs index 4d691be8b..5e0ff2f25 100644 --- a/src/GameLogic/PlugIns/IAreaSkillPlugIn.cs +++ b/src/GameLogic/PlugIns/IAreaSkillPlugIn.cs @@ -23,5 +23,6 @@ public interface IAreaSkillPlugIn : IStrategyPlugIn /// The target. /// The skill entry. /// The target area center. - ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter); + /// Hit info produced by the skill. + ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo); } \ No newline at end of file diff --git a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs index 6135da14a..142cc96a9 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs @@ -171,7 +171,7 @@ public override void Initialize() this.CreateSkill(SkillNumber.SpellofRestriction, "Spell of Restriction", CharacterClasses.All, distance: 3, manaConsumption: 30, elementalModifier: ElementalType.Ice, cooldownMinutes: 5); this.CreateSkill(SkillNumber.SpellofPursuit, "Spell of Pursuit", CharacterClasses.All, manaConsumption: 30, elementalModifier: ElementalType.Ice, cooldownMinutes: 10); this.CreateSkill(SkillNumber.ShieldBurn, "Shield-Burn", CharacterClasses.All, distance: 3, manaConsumption: 30, elementalModifier: ElementalType.Ice, cooldownMinutes: 5); - this.CreateSkill(SkillNumber.DrainLife, "Drain Life", CharacterClasses.AllSummoners, DamageType.Curse, 35, 6, manaConsumption: 50, energyRequirement: 150); + this.CreateSkill(SkillNumber.DrainLife, "Drain Life", CharacterClasses.AllSummoners, DamageType.Curse, 35, 6, manaConsumption: 50, energyRequirement: 150, skillType: SkillType.AreaSkillExplicitTarget); this.CreateSkill(SkillNumber.ChainLightning, "Chain Lightning", CharacterClasses.AllSummoners, DamageType.Curse, 70, 6, manaConsumption: 85, energyRequirement: 245, skillType: SkillType.AreaSkillExplicitTarget, skillTarget: SkillTarget.Explicit); this.CreateSkill(SkillNumber.DamageReflection, "Damage Reflection", CharacterClasses.AllSummoners, distance: 5, abilityConsumption: 10, manaConsumption: 40, energyRequirement: 375); this.CreateSkill(SkillNumber.Berserker, "Berserker", CharacterClasses.AllSummoners, DamageType.Curse, distance: 5, abilityConsumption: 50, manaConsumption: 100, energyRequirement: 620); From 2890a55c0959ffffedae70846e2c2773e743b1bd Mon Sep 17 00:00:00 2001 From: bernat Date: Sat, 24 Aug 2024 18:29:18 +0200 Subject: [PATCH 2/3] fix docs --- src/GameLogic/IAttackable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GameLogic/IAttackable.cs b/src/GameLogic/IAttackable.cs index 578b81f41..f3234b1c3 100644 --- a/src/GameLogic/IAttackable.cs +++ b/src/GameLogic/IAttackable.cs @@ -65,6 +65,7 @@ public interface IAttackable : IIdentifiable, ILocateable /// The skill. /// If set to true, the attacker did a combination of skills. /// The damage factor. + /// Returns information about the damage inflicted. ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0); /// From 7640e9f45da980087d56a650f48d616e0fe53537 Mon Sep 17 00:00:00 2001 From: bernat Date: Thu, 29 Aug 2024 19:06:57 +0200 Subject: [PATCH 3/3] Add UpdatePlugin for Drain Life fix --- .../Updates/FixDrainLifeSkillUpdate.cs | 55 +++++++++++++++++++ .../Initialization/Updates/UpdateVersion.cs | 5 ++ 2 files changed, 60 insertions(+) create mode 100644 src/Persistence/Initialization/Updates/FixDrainLifeSkillUpdate.cs diff --git a/src/Persistence/Initialization/Updates/FixDrainLifeSkillUpdate.cs b/src/Persistence/Initialization/Updates/FixDrainLifeSkillUpdate.cs new file mode 100644 index 000000000..0733ef302 --- /dev/null +++ b/src/Persistence/Initialization/Updates/FixDrainLifeSkillUpdate.cs @@ -0,0 +1,55 @@ +// +// 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.DataModel.Configuration.Items; +using MUnique.OpenMU.GameLogic; +using MUnique.OpenMU.Persistence.Initialization.Skills; +using MUnique.OpenMU.PlugIns; + +/// +/// This adds the items required to enter the kalima map. +/// +[PlugIn(PlugInName, PlugInDescription)] +[Guid("A8827A3C-7F52-47CF-9EA5-562A9C06B986")] +public class FixDrainLifeSkillUpdate : UpdatePlugInBase +{ + /// + /// The plug in name. + /// + internal const string PlugInName = "Fix Drain Life Skill"; + + /// + /// The plug in description. + /// + internal const string PlugInDescription = "Updates the attributes of the summoner's Drain Life skill to make it work properly."; + + /// + public override UpdateVersion Version => UpdateVersion.FixDrainLifeSkill; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override bool IsMandatory => false; + + /// + public override DateTime CreatedAt => new(2024, 08, 29, 18, 0, 0, DateTimeKind.Utc); + + /// + protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + var drainLife = gameConfiguration.Skills.First(x => x.Number == (short)SkillNumber.DrainLife); + drainLife.SkillType = SkillType.AreaSkillExplicitTarget; + } +} \ No newline at end of file diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs index 41757fadf..2e6a66b2a 100644 --- a/src/Persistence/Initialization/Updates/UpdateVersion.cs +++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs @@ -139,4 +139,9 @@ public enum UpdateVersion /// The version of the . /// FixAncientDiscriminators = 26, + + /// + /// The version of the . + /// + FixDrainLifeSkill = 27, } \ No newline at end of file