Files
AdamuSw/src/GameLogic/PlayerActions/HitAction.cs
2026-07-14 19:00:35 +03:00

123 lines
4.9 KiB
C#

// <copyright file="HitAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlugIns;
using MUnique.OpenMU.GameLogic.Views.World;
/// <summary>
/// Action to hit targets without a skill with pure melee damage.
/// </summary>
public class HitAction
{
/// <summary>
/// Hits the specified target by the specified player.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="target">The target.</param>
/// <param name="attackAnimation">The attack animation.</param>
/// <param name="lookingDirection">The looking direction.</param>
public async ValueTask HitAsync(Player player, IAttackable target, byte attackAnimation, Direction lookingDirection)
{
if (player.Attributes is not { } attributes)
{
return;
}
if (attributes[Stats.IsStunned] > 0)
{
player.Logger.LogWarning("Probably Hacker - player {Player} is attacking in stunned state", player);
return;
}
if (attributes[Stats.IsAsleep] > 0)
{
player.Logger.LogWarning("Probably Hacker - player {Player} is attacking in asleep state", player);
return;
}
if (player.IsAtSafezone())
{
player.Logger.LogWarning("Probably Hacker - player {Player} is attacking from safezone", player);
return;
}
if (player.GameContext.PlugInManager.GetPlugInPoint<ISpeedHackCheatCheckPlugIn>() is { } speedCheck)
{
var eventArgs = new SpeedHackCheckEventArgs();
await speedCheck.AttackCheatCheckAsync(player, eventArgs).ConfigureAwait(false);
if (eventArgs.IsCheatDetected)
{
return;
}
}
if (target.IsAtSafezone())
{
return;
}
if (target is IObservable targetAsObservable)
{
using var readerLock = await targetAsObservable.ObserverLock.ReaderLockAsync();
if (!targetAsObservable.Observers.Contains(player))
{
// Target out of range
return;
}
}
player.Rotation = lookingDirection;
await target.AttackByAsync(player, null, false).ConfigureAwait(false);
if (player.Attributes?[Stats.TransformationSkin] is { } skin and not 0
&& await this.ApplySkinnedMonstersSkillAsync(player, target, (short)skin).ConfigureAwait(false) is var (skill, effectApplied))
{
await player.ForEachWorldObserverAsync<IShowSkillAnimationPlugIn>(p => p.ShowSkillAnimationAsync(player, target, skill, effectApplied), true).ConfigureAwait(false);
return;
}
await player.ForEachWorldObserverAsync<IShowAnimationPlugIn>(p => p.ShowAnimationAsync(player, attackAnimation, target, lookingDirection), false).ConfigureAwait(false);
}
private async ValueTask<(Skill Skill, bool EffectApplied)?> ApplySkinnedMonstersSkillAsync(Player player, IAttackable target, short skin)
{
var effectApplied = false;
if (player.GameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == skin)?.AttackSkill
is not { ElementalModifierTarget: not null } skill)
{
return null;
}
var modifier = skill.ElementalModifierTarget!;
var resistance = target.Attributes[modifier];
if (resistance >= 255 || !Rand.NextRandomBool(1 / (resistance + 1)))
{
return (skill, effectApplied);
}
// Currently, we just support one effect for monsters.
// E.g. Poison for Poison BullFighters.
if (skill.MagicEffectDef is { Duration: not null } effectDefinition
&& !target.MagicEffectList.ActiveEffects.ContainsKey(effectDefinition.Number)
&& effectDefinition.PowerUpDefinitions.FirstOrDefault() is { Boost: not null } powerUpDef)
{
var powerUp = target.Attributes.CreateElement(powerUpDef);
var powerUpDuration = target.Attributes.CreateDurationElement(effectDefinition.Duration);
var magicEffect = powerUpDef.TargetAttribute == Stats.IsPoisoned
? new PoisonMagicEffect(powerUp, effectDefinition, TimeSpan.FromSeconds(powerUpDuration.Value), player, target)
: new MagicEffect(powerUp, effectDefinition, TimeSpan.FromSeconds(powerUpDuration.Value));
await target.MagicEffectList.AddEffectAsync(magicEffect).ConfigureAwait(false);
effectApplied = true;
}
if (modifier == Stats.LightningResistance)
{
await target.MoveRandomlyAsync().ConfigureAwait(false);
}
return (skill, effectApplied);
}
}